import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useReducer,
  useRef,
  type ReactNode,
} from "react";
import {
  getEdition,
  readLicense,
  storeLicense,
  type Edition,
  type LicenseInfo,
} from "../services/licenseService";

/**
 * Machine-level license context — loaded ONCE at boot, above ProfileProvider.
 *
 * Modeled on ProfileContext (createContext<T | null>, useReducer, consumer hook
 * that throws). Mounting above the profile layer means it survives the
 * `BrowserRouter key={refreshKey}` remount that a profile switch triggers — the
 * license is a property of the machine, not of the active profile.
 *
 * Error recovery (CWE-703): the provider is a SPOF. If the boot invoke throws,
 * we expose a NEUTRAL state (previous edition preserved, `status: "error"`) and
 * retry with capped exponential backoff. Consumers must render a neutral
 * placeholder while `status !== "ready"` — never the upsell — so a transient IPC
 * failure never locks a paying user out.
 *
 * Key validation is orthogonal to that load lifecycle: a rejected `submitKey`
 * leaves the stored license — and therefore `status` — untouched, and surfaces
 * `validationError` instead. The retry loop keys off `status === "error"`, so
 * it can never arm on a bad key and auto-clear the message the user is reading,
 * and `ready` consumers never regress on a typo'd key.
 */

type LicenseStatus = "idle" | "loading" | "ready" | "error";

interface LicenseState {
  status: LicenseStatus;
  edition: Edition;
  info: LicenseInfo | null;
  /** Load-lifecycle error — target of the CWE-703 retry loop. */
  error: string | null;
  validating: boolean;
  /** Key-submission error — never retried, cleared on the next submit. */
  validationError: string | null;
}

type LicenseAction =
  | { type: "LOAD_START" }
  | { type: "LOAD_DONE"; edition: Edition; info: LicenseInfo | null }
  | { type: "LOAD_ERROR"; error: string }
  | { type: "VALIDATE_START" }
  | { type: "VALIDATE_DONE"; info: LicenseInfo }
  | { type: "VALIDATE_ERROR"; error: string };

/** Exported for unit tests only — not part of the context API. */
export const initialLicenseState: LicenseState = {
  status: "idle",
  edition: "free",
  info: null,
  error: null,
  validating: false,
  validationError: null,
};

/** Exported for unit tests only — not part of the context API. */
export function licenseReducer(state: LicenseState, action: LicenseAction): LicenseState {
  switch (action.type) {
    case "LOAD_START":
      return { ...state, status: "loading", error: null };
    case "LOAD_DONE":
      return { ...state, status: "ready", edition: action.edition, info: action.info, error: null };
    case "LOAD_ERROR":
      // Preserve the last-known edition: an error must not downgrade a paying
      // user to the upsell (that is what fail-closed + `ready` guard protect).
      return { ...state, status: "error", error: action.error };
    case "VALIDATE_START":
      return { ...state, validating: true, validationError: null };
    case "VALIDATE_DONE":
      return {
        status: "ready",
        edition: action.info.edition,
        info: action.info,
        error: null,
        validating: false,
        validationError: null,
      };
    case "VALIDATE_ERROR":
      // Status untouched: "ready" stays ready (no gating flash on a typo'd
      // key), and a boot-error retry loop keeps running through the failure.
      return { ...state, validating: false, validationError: action.error };
  }
}

type SubmitKeyResult =
  | { ok: true; info: LicenseInfo }
  | { ok: false; error: string };

interface LicenseContextValue {
  status: LicenseStatus;
  edition: Edition;
  features: string[];
  info: LicenseInfo | null;
  error: string | null;
  validating: boolean;
  validationError: string | null;
  refresh: () => Promise<void>;
  submitKey: (key: string) => Promise<SubmitKeyResult>;
}

const LicenseContext = createContext<LicenseContextValue | null>(null);

// Capped exponential backoff for boot-error retries: 1s, 2s, 4s, ... max 30s.
const RETRY_BASE_MS = 1000;
const RETRY_MAX_MS = 30_000;

export function LicenseProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(licenseReducer, initialLicenseState);
  const retryAttempt = useRef(0);

  const refresh = useCallback(async () => {
    dispatch({ type: "LOAD_START" });
    try {
      const [edition, info] = await Promise.all([getEdition(), readLicense()]);
      dispatch({ type: "LOAD_DONE", edition, info });
    } catch (e) {
      dispatch({
        type: "LOAD_ERROR",
        error: e instanceof Error ? e.message : String(e),
      });
    }
  }, []);

  const submitKey = useCallback(async (key: string): Promise<SubmitKeyResult> => {
    dispatch({ type: "VALIDATE_START" });
    try {
      const info = await storeLicense(key);
      dispatch({ type: "VALIDATE_DONE", info });
      return { ok: true, info };
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e);
      dispatch({ type: "VALIDATE_ERROR", error: message });
      return { ok: false, error: message };
    }
  }, []);

  // Load once at boot.
  useEffect(() => {
    void refresh();
  }, [refresh]);

  // Error recovery: retry with capped exponential backoff (CWE-703). Load
  // errors only by construction — validation failures never set status.
  useEffect(() => {
    if (state.status === "ready") {
      retryAttempt.current = 0;
      return;
    }
    if (state.status !== "error") return;
    const attempt = retryAttempt.current;
    retryAttempt.current = attempt + 1;
    const delay = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** attempt);
    const timer = window.setTimeout(() => {
      void refresh();
    }, delay);
    return () => window.clearTimeout(timer);
  }, [state.status, refresh]);

  const value: LicenseContextValue = {
    status: state.status,
    edition: state.edition,
    features: state.info?.features ?? [],
    info: state.info,
    error: state.error,
    validating: state.validating,
    validationError: state.validationError,
    refresh,
    submitKey,
  };

  return <LicenseContext.Provider value={value}>{children}</LicenseContext.Provider>;
}

export function useLicenseContext(): LicenseContextValue {
  const ctx = useContext(LicenseContext);
  if (!ctx) throw new Error("useLicenseContext must be used within LicenseProvider");
  return ctx;
}
