const crypto = require("crypto");
const fs = require("fs");
const os = require("os");
const path = require("path");
const DEFAULT_PRODUCT = {
  appName: "Liberty Raceworks Pro",
  productId: "liberty-raceworks-pro",
  tier: "pro",
  trialDays: 7,
  offlineGraceDays: 14,
  allowedGraphics: ["blank", "lineup", "running", "topbanner", "sidebanner", "results", "leaders", "top5", "podium", "weather", "ticker", "story"]
};

function readJsonIfExists(filePath) {
  try {
    if (!fs.existsSync(filePath)) return null;
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch (_err) {
    return null;
  }
}

function loadProductConfig(rootDir) {
  const config = readJsonIfExists(path.join(rootDir, "product-config.json")) || {};
  return {
    ...DEFAULT_PRODUCT,
    ...config,
    allowedGraphics: Array.isArray(config.allowedGraphics) ? config.allowedGraphics : DEFAULT_PRODUCT.allowedGraphics
  };
}

function machineFingerprint() {
  const raw = [
    os.hostname(),
    os.platform(),
    os.arch(),
    os.userInfo().username,
    JSON.stringify(os.networkInterfaces())
  ].join("|");
  return crypto.createHash("sha256").update(raw).digest("hex");
}

function daysFromNow(days) {
  return new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
}

function verifySignedToken(token, publicKey) {
  if (!token || typeof token !== "string") return null;
  if (!publicKey) return null;
  const parts = token.split(".");
  if (parts.length !== 2) return null;
  const payloadText = Buffer.from(parts[0], "base64url").toString("utf8");
  const signature = Buffer.from(parts[1], "base64url");
  const verifier = crypto.createVerify("RSA-SHA256");
  verifier.update(payloadText);
  verifier.end();
  if (!verifier.verify(publicKey, signature)) return null;
  return JSON.parse(payloadText);
}

function localTokenStatus(state, productConfig, publicKey) {
  const payload = verifySignedToken(state.signedToken, publicKey);
  if (!payload) return null;
  if (payload.productId !== productConfig.productId) return null;
  if (payload.tier !== productConfig.tier) return null;
  if (payload.machineId !== state.machineId) return null;
  const now = Date.now();
  const expiresAt = payload.expiresAt ? Date.parse(payload.expiresAt) : 0;
  const offlineValidUntil = payload.offlineValidUntil ? Date.parse(payload.offlineValidUntil) : 0;
  if (expiresAt && expiresAt <= now) return { active: false, status: "expired", payload };
  if (offlineValidUntil && offlineValidUntil <= now) return { active: false, status: "check-in-required", payload };
  return { active: true, status: "active", payload };
}

function postJson(url, payload, timeoutMs) {
  return new Promise((resolve, reject) => {
    let target;
    try {
      target = new URL(url);
    } catch (_err) {
      reject(new Error("License server URL is not valid."));
      return;
    }
    const client = target.protocol === "https:" ? require("https") : target.protocol === "http:" ? require("http") : null;
    if (!client) {
      reject(new Error("License server URL must start with http:// or https://."));
      return;
    }
    const body = JSON.stringify(payload);
    const request = client.request(target, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(body)
      },
      timeout: timeoutMs
    }, response => {
      let text = "";
      response.on("data", chunk => {
        text += chunk;
      });
      response.on("end", () => {
        let data = {};
        try {
          data = JSON.parse(text || "{}");
        } catch (err) {
          reject(new Error(`Could not parse license server response: ${err.message}`));
          return;
        }
        if (response.statusCode < 200 || response.statusCode >= 300) {
          reject(new Error(data.error || `License server returned HTTP ${response.statusCode}`));
          return;
        }
        resolve(data);
      });
    });
    request.on("timeout", () => request.destroy(new Error("License server timed out.")));
    request.on("error", reject);
    request.end(body);
  });
}

function createLicenseManager({ uploadDir, productConfig, fetchTimeoutMs = 8000 }) {
  const licenseDir = path.join(uploadDir, "License");
  const licenseFile = path.join(licenseDir, "license-state.json");
  const licenseServerUrl = String(process.env.LICENSE_SERVER_URL || productConfig.licenseServerUrl || "").replace(/\/+$/, "");
  // Public key verifies server-signed license tokens. Baked into each build's
  // product-config.json (safe to ship); env var overrides for development.
  const licensePublicKey = String(process.env.LICENSE_PUBLIC_KEY || productConfig.licensePublicKey || "").trim();
  const licenseRequired = process.env.LICENSE_REQUIRED === "0"
    ? false
    : productConfig.licenseRequired !== false;

  function readState() {
    return readJsonIfExists(licenseFile) || {
      status: "unlicensed",
      productId: productConfig.productId,
      tier: productConfig.tier,
      machineId: machineFingerprint(),
      trialStartedAt: "",
      trialExpiresAt: "",
      activatedAt: "",
      expiresAt: "",
      productKey: "",
      activationId: "",
      lastCheckedAt: "",
      signedToken: "",
      message: ""
    };
  }

  async function writeState(state) {
    await fs.promises.mkdir(licenseDir, { recursive: true });
    await fs.promises.writeFile(licenseFile, `${JSON.stringify(state, null, 2)}\n`);
    return state;
  }

  function publicState() {
    const state = readState();
    const now = Date.now();
    const trialExpires = state.trialExpiresAt ? Date.parse(state.trialExpiresAt) : 0;
    const licenseExpires = state.expiresAt ? Date.parse(state.expiresAt) : 0;
    const tokenStatus = localTokenStatus(state, productConfig, licensePublicKey);
    const trialActive = state.status === "trial" && trialExpires > now;
    // When a public key is configured, the server-signed token is the ONLY proof
    // of a paid activation — a local "active" flag without a valid token does not
    // count (this is what blocks license-file tampering). Without a public key
    // (honor-system/legacy builds) we fall back to the stored status.
    const enforcing = Boolean(licensePublicKey);
    const activated = tokenStatus
      ? tokenStatus.active
      : enforcing
        ? false
        : state.status === "active" && (!licenseExpires || licenseExpires > now);
    const lockedStatus = tokenStatus
      ? tokenStatus.status
      : enforcing && state.status === "active"
        ? "check-in-required"
        : state.status === "active"
          ? "expired"
          : state.status;
    return {
      status: activated ? "active" : trialActive ? "trial" : lockedStatus,
      active: activated || trialActive,
      productId: productConfig.productId,
      tier: productConfig.tier,
      machineId: state.machineId,
      trialStartedAt: state.trialStartedAt,
      trialExpiresAt: state.trialExpiresAt,
      activatedAt: state.activatedAt,
      expiresAt: state.expiresAt,
      activationId: state.activationId,
      lastCheckedAt: state.lastCheckedAt,
      offlineValidUntil: tokenStatus && tokenStatus.payload ? tokenStatus.payload.offlineValidUntil || "" : "",
      signedTokenPresent: Boolean(state.signedToken),
      message: state.message || "",
      licenseServerConfigured: Boolean(licenseServerUrl),
      licenseRequired
    };
  }

  async function startTrial() {
    const current = readState();
    if (current.trialStartedAt) return publicState();
    await writeState({
      ...current,
      status: "trial",
      trialStartedAt: new Date().toISOString(),
      trialExpiresAt: daysFromNow(Number(productConfig.trialDays) || 7),
      message: ""
    });
    return publicState();
  }

  async function activate(productKey) {
    const key = String(productKey || "").trim();
    if (!key) throw new Error("Enter a product key.");
    if (!licenseServerUrl) {
      await writeState({
        ...readState(),
        status: "pending",
        productKey: key,
        message: "Product key saved. Configure LICENSE_SERVER_URL to verify activations."
      });
      return publicState();
    }
    const result = await postJson(`${licenseServerUrl}/activate`, {
      productKey: key,
      productId: productConfig.productId,
      tier: productConfig.tier,
      machineId: machineFingerprint(),
      machineName: os.hostname(),
      appVersion: productConfig.version || "1.0.0-beta.1"
    }, fetchTimeoutMs);
    if (!result || result.ok === false) throw new Error(result && result.error ? result.error : "Activation rejected.");
    await writeState({
      ...readState(),
      status: "active",
      productKey: key,
      activationId: result.activationId || "",
      activatedAt: new Date().toISOString(),
      expiresAt: result.expiresAt || "",
      signedToken: result.signedToken || "",
      lastCheckedAt: new Date().toISOString(),
      message: ""
    });
    return publicState();
  }

  async function refresh() {
    const current = readState();
    if (!current.productKey && !current.activationId) return publicState();
    if (!licenseServerUrl) return publicState();
    let result;
    try {
      result = await postJson(`${licenseServerUrl}/refresh`, {
        productKey: current.productKey,
        activationId: current.activationId,
        productId: productConfig.productId,
        tier: productConfig.tier,
        machineId: current.machineId,
        appVersion: productConfig.version || "1.0.0-beta.1"
      }, fetchTimeoutMs);
    } catch (_networkErr) {
      // Couldn't reach the server (offline / timeout). Keep running on the
      // existing signed token until its offline grace window lapses; never lock
      // a customer out because of a flaky race-track connection.
      return publicState();
    }
    if (!result || result.ok === false) {
      // The server answered and rejected the license (revoked, canceled, expired,
      // or this machine was released). Drop the token so the program locks now.
      await writeState({
        ...current,
        status: "revoked",
        signedToken: "",
        expiresAt: "",
        lastCheckedAt: new Date().toISOString(),
        message: (result && result.error) || "This license is no longer valid."
      });
      return publicState();
    }
    await writeState({
      ...current,
      status: "active",
      expiresAt: result.expiresAt || current.expiresAt,
      signedToken: result.signedToken || current.signedToken || "",
      lastCheckedAt: new Date().toISOString(),
      message: ""
    });
    return publicState();
  }

  async function deactivate() {
    const current = readState();
    if (licenseServerUrl && (current.productKey || current.activationId)) {
      await postJson(`${licenseServerUrl}/deactivate`, {
        productKey: current.productKey,
        activationId: current.activationId,
        productId: productConfig.productId,
        machineId: current.machineId
      }, fetchTimeoutMs).catch(() => null);
    }
    await writeState({
      ...current,
      status: "unlicensed",
      productKey: "",
      activationId: "",
      activatedAt: "",
      expiresAt: "",
      signedToken: "",
      message: ""
    });
    return publicState();
  }

  return {
    productConfig,
    publicState,
    startTrial,
    activate,
    refresh,
    deactivate
  };
}

module.exports = { loadProductConfig, createLicenseManager };
