/**
 * Device fingerprint + free-trial hook.
 *
 * Strategy:
 *   1. Resolve a stable HARDWARE fingerprint that SURVIVES app reinstall:
 *      - Android (Capacitor)      → ANDROID_ID via @capacitor/device
 *      - Samsung Tizen TV         → tizen.systeminfo "duid" (per-device
 *                                   unique ID, survives factory-reset on
 *                                   most models)
 *      - LG webOS TV              → luna://com.webos.service.sm
 *                                   "deviceid/getIDs" (per-device IDFA)
 *      - Web browsers / unknown   → a stable UUID stored in localStorage
 *                                   (best-effort; clearing browser data
 *                                   resets it — acceptable, since the
 *                                   real targets are the native shells)
 *   2. POST it to /api/trial/status with the detected platform and cache
 *      the response in React state.
 *   3. Re-check every 6 hours while the app is open so an expiring trial
 *      flips the guard without requiring a restart.
 *
 * Trial length is enforced SERVER-SIDE based on the `platform` we send:
 *   - tizen / webos   → 15 days
 *   - everything else → 30 days
 * So even if a Tizen / webOS user uninstalls and reinstalls the app
 * 1,000 times, the server keeps counting from `first_seen_at` and the
 * lock screen will appear on day 16.
 */

import { useEffect, useState } from "react";
import { Capacitor } from "@capacitor/core";
import { getPlatform, isTizen, isWebOS } from "@/lib/platform";

const HW_ID_KEY = "nadi_hw_id";
const TRIAL_CACHE_KEY = "nadi_trial_cache_v1";

export type TrialStatus = {
  hardware_id: string;
  first_seen_at: string;
  last_seen_at: string;
  trial_days: number;
  days_used: number;
  days_remaining: number;
  trial_expired: boolean;
  is_premium: boolean;
  premium_expiry: string | null;
};

function apiBase(): string {
  // @ts-expect-error Vite inlines import.meta.env at build time
  return import.meta.env.VITE_BACKEND_URL
    // @ts-expect-error Vite inlines import.meta.env at build time
    || import.meta.env.REACT_APP_BACKEND_URL
    // @ts-expect-error — last-resort global injected by the native shell
    || (globalThis as any).REACT_APP_BACKEND_URL
    || "";
}

function uuidv4(): string {
  // Crypto-random UUID — works on every modern browser + Android WebView.
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

/** Resolve Samsung Tizen device unique ID via the Tizen Web Device API.
 *  `tizen.systeminfo.getCapability("http://tizen.org/system/tizenid")`
 *  returns the device's DUID which is stable across factory resets on
 *  most 2015+ TVs. No `tizen:privilege` is required — capability reads
 *  are public. */
function resolveTizenId(): string | null {
  try {
    const tizen = (window as any).tizen;
    if (!tizen?.systeminfo?.getCapability) return null;
    // Some firmwares expose `duid`, others only `tizenid`. Try both.
    const candidates = [
      "http://tizen.org/system/tizenid",
      "http://tizen.org/system/duid",
    ];
    for (const key of candidates) {
      try {
        const v = tizen.systeminfo.getCapability(key);
        if (v && typeof v === "string" && v.length >= 4) return `tizen-${v}`;
      } catch {
        /* try next */
      }
    }
  } catch {
    /* ignore */
  }
  return null;
}

/** Resolve LG webOS device unique ID via the `sm` service. Uses an LS2
 *  call which is allowed in unprivileged web apps that ship in an `.ipk`
 *  to the public LG Content Store. Falls back to `webOS.fetchAppInfo`
 *  device id if available. */
function resolveWebOSId(): Promise<string | null> {
  return new Promise((resolve) => {
    try {
      const webOS = (window as any).webOS;
      if (!webOS?.service?.request) {
        resolve(null);
        return;
      }
      let settled = false;
      const done = (v: string | null) => {
        if (settled) return;
        settled = true;
        resolve(v);
      };
      // 1s safety timeout — webOS LS2 occasionally never calls back when
      // the user hasn't accepted device-info consent. We accept the
      // localStorage fallback in that case.
      const safety = setTimeout(() => done(null), 1000);

      webOS.service.request("luna://com.webos.service.sm", {
        method: "deviceid/getIDs",
        parameters: { idType: ["LGUDID"] },
        onSuccess: (res: any) => {
          clearTimeout(safety);
          try {
            const idList = res?.idList;
            const lgudid =
              Array.isArray(idList) && idList[0] && idList[0].idValue
                ? String(idList[0].idValue)
                : null;
            done(lgudid ? `webos-${lgudid}` : null);
          } catch {
            done(null);
          }
        },
        onFailure: () => {
          clearTimeout(safety);
          done(null);
        },
      });
    } catch {
      resolve(null);
    }
  });
}

async function resolveHardwareId(): Promise<string> {
  // ── 1. Samsung Tizen TV ────────────────────────────────────────────
  if (isTizen()) {
    const id = resolveTizenId();
    if (id) return id;
  }
  // ── 2. LG webOS TV ─────────────────────────────────────────────────
  if (isWebOS()) {
    const id = await resolveWebOSId();
    if (id) return id;
  }
  // ── 3. Android (Capacitor) ─────────────────────────────────────────
  if (Capacitor.isNativePlatform && Capacitor.isNativePlatform()) {
    try {
      // Lazy-load to avoid breaking the web preview if the package isn't there.
      const mod: any = await import("@capacitor/device").catch(() => null);
      if (mod?.Device?.getId) {
        const info = await mod.Device.getId();
        // identifier on Android 10+ = ANDROID_ID
        const id = info?.identifier || info?.uuid;
        if (id) return String(id);
      }
    } catch {
      // fall through to localStorage fallback
    }
  }
  // ── 4. Web / desktop fallback (stable per browser profile) ─────────
  try {
    const cached = localStorage.getItem(HW_ID_KEY);
    if (cached) return cached;
    const fresh = "web-" + uuidv4();
    localStorage.setItem(HW_ID_KEY, fresh);
    return fresh;
  } catch {
    // Private-browsing / SSR — return a throwaway id. The user will
    // simply re-start their trial each session, which is fine in the
    // lowest-tier fallback.
    return "ephemeral-" + uuidv4();
  }
}

async function fetchStatus(hardware_id: string): Promise<TrialStatus> {
  const base = apiBase();
  const platform = getPlatform();
  const res = await fetch(`${base}/api/trial/status`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      hardware_id,
      platform,
      app_version: "1.3.0",
    }),
  });
  if (!res.ok) throw new Error(`trial/status HTTP ${res.status}`);
  return (await res.json()) as TrialStatus;
}

function readCache(): TrialStatus | null {
  try {
    const raw = localStorage.getItem(TRIAL_CACHE_KEY);
    return raw ? (JSON.parse(raw) as TrialStatus) : null;
  } catch {
    return null;
  }
}

function writeCache(s: TrialStatus): void {
  try {
    localStorage.setItem(TRIAL_CACHE_KEY, JSON.stringify(s));
  } catch {
    /* ignore */
  }
}

const SIX_HOURS = 6 * 60 * 60 * 1000;

export function useTrialStatus(): {
  trial: TrialStatus | null;
  loading: boolean;
  error: string | null;
  refresh: () => Promise<void>;
} {
  const [trial, setTrial] = useState<TrialStatus | null>(() => readCache());
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  const run = async () => {
    setError(null);
    try {
      const hw = await resolveHardwareId();
      const status = await fetchStatus(hw);
      setTrial(status);
      writeCache(status);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
      // Keep serving whatever is cached so the app doesn't hard-lock
      // when the backend is briefly unreachable.
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    void run();
    const t = window.setInterval(() => {
      void run();
    }, SIX_HOURS);
    return () => window.clearInterval(t);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return { trial, loading, error, refresh: run };
}

/** True when ads should be shown: trial still active OR no status yet AND user is not premium. */
export function shouldShowAds(trial: TrialStatus | null, userIsPremium: boolean): boolean {
  if (userIsPremium) return false;
  if (!trial) return true; // conservative: trial not yet known → show ads
  if (trial.is_premium) return false;
  return !trial.trial_expired;
}
