import { openStore } from "@/lib/data/store";

/**
 * Talking to the licence server.
 *
 * It fails open by design: anything that's the server's own fault answers
 * as valid, so a paying customer is never switched off by a database blip
 * or a restored backup. Only a deliberate withdrawal — revoked, suspended,
 * wrong domain, deactivated — turns something off.
 *
 * That decision is worth keeping. A false negative costs a customer; a
 * false positive costs a day of unlicensed use.
 */

/** The licence server. Everything below hangs off these two. */
export const API_ROOT = "https://license.hifod.com/api";
const BASE = `${API_ROOT}/license`;

/** How this install identifies itself. */
async function siteDomain(): Promise<string> {
  const store = await openStore();
  const site = await store.settings();
  const url = String(
    site.siteUrl ?? ""
  );

  try {
    return new URL(url).hostname;
  } catch {
    return "localhost";
  }
}

export type Activation = {
  license: string;
  /** The version this copy is running, sent with periodic checks. */
  version?: string;
  token: string;
  product: string;
  domain: string;
  activatedAt: string;
  /** When to ask again. The server decides. */
  nextCheck?: string;
};

/**
 * Redeems a purchase and activates it in one step.
 *
 * Takes our own key, a ScriptBull order number, or a PortaSale code — the
 * server sorts out which.
 */
export async function redeem(
  identifier: string,
  product: string,
  version: string
): Promise<{ ok: true; license: string } | { ok: false; error: string }> {
  const domain = await siteDomain();

  try {
    const res = await fetch(
      `${API_ROOT}/request-license.php`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          domain,
          order_number: identifier,
          product,
          version,
        }),
      }
    );

    const data = await res.json().catch(() => null);

    if (!res.ok || !data?.success) {
      return {
        ok: false,
        error: String(data?.error ?? data?.message ?? "That key wasn't accepted"),
      };
    }

    return { ok: true, license: String(data.license ?? data.license_key ?? identifier) };
  } catch {
    return {
      ok: false,
      error: "Couldn't reach the licence server. Try again in a moment.",
    };
  }
}

/** Activates a key for this domain and returns the token to keep. */
export async function activate(
  license: string,
  product: string,
  version: string
): Promise<{ ok: true; token: string } | { ok: false; error: string }> {
  const domain = await siteDomain();

  try {
    const res = await fetch(`${BASE}/activate`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        license,
        domain,
        product,
        version,
        php_version: process.version,
      }),
    });

    const data = await res.json().catch(() => null);

    if (!res.ok || !data?.success || !data?.token) {
      return {
        ok: false,
        error: String(data?.error ?? "That key couldn't be activated here"),
      };
    }

    return { ok: true, token: String(data.token) };
  } catch {
    return { ok: false, error: "Couldn't reach the licence server" };
  }
}

/**
 * Re-checks a licence.
 *
 * Only the four enforcement cases mean anything is wrong. Everything else
 * — including no answer at all — is treated as fine, which is what the
 * server itself does.
 */
export async function check(
  activation: Activation
): Promise<{
  valid: boolean;
  updateAvailable?: boolean;
  latestVersion?: string;
  nextCheck?: string;
  note?: string;
}> {
  try {
    const res = await fetch(`${BASE}/check`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        license: activation.license,
        token: activation.token,
        domain: activation.domain,
        product: activation.product,
        // What this copy is running, so the server can answer accurately
        // rather than guessing. We still compare versions ourselves.
        version: activation.version,
      }),
    });

    const data = await res.json().catch(() => null);

    // A failure is not a withdrawal. Their own guidance: keep running.
    if (!res.ok || !data || data.success === false) {
      return { valid: true, note: "Couldn't check just now" };
    }

    return {
      valid: data.valid !== false,
      updateAvailable: Boolean(data.update_available),
      latestVersion: data.latest_version ? String(data.latest_version) : undefined,
      nextCheck: data.next_check ? String(data.next_check) : undefined,
      note: data.note ? String(data.note) : undefined,
    };
  } catch {
    return { valid: true, note: "Couldn't reach the licence server" };
  }
}

/** Releases a key so it can be used on another domain. */
export async function deactivate(activation: Activation): Promise<boolean> {
  try {
    const res = await fetch(`${BASE}/deactivate`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        license: activation.license,
        token: activation.token,
        domain: activation.domain,
        product: activation.product,
      }),
    });

    return res.ok;
  } catch {
    return false;
  }
}

/**
 * Fetches a theme package, on a valid key, in one request.
 *
 * One request matters: if the check and the download are separate calls,
 * anyone can skip the first. This way a copy without a key has nothing to
 * fetch — the theme genuinely isn't in it.
 */
export async function fetchTheme(
  license: string,
  slug: string
): Promise<
  | { ok: true; pkg: Record<string, unknown> }
  | { ok: false; error: string }
> {
  const domain = await siteDomain();

  try {
    const res = await fetch(
      `${API_ROOT}/theme-download.php`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ license, domain, theme: slug }),
      }
    );

    if (res.status === 403 || res.status === 401) {
      return { ok: false, error: "That key isn't valid for this theme" };
    }

    const data = await res.json().catch(() => null);

    if (!res.ok || !data?.manifest || !data?.tokens) {
      return {
        ok: false,
        error: String(data?.error ?? "The theme couldn't be downloaded"),
      };
    }

    return { ok: true, pkg: data };
  } catch {
    return {
      ok: false,
      error: "Couldn't reach the shop. Check the site can get out to the internet.",
    };
  }
}
