import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import type { LicensePayload, LicenseValidationResult, StoredLicense } from './types';
import { matchesMachine } from './machine-id';

// ─── Embedded Public Key (NEVER ship private key to clients) ─────────────────
const EMBEDDED_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4VvderF4tF3gUV9WxxWB8YwYZvSG
rGB3D0fXq2PlaxQqn+jzp/CzNJIxSVA6HOzoAeKMSBQnSd6tOeA+q2VeIg==
-----END PUBLIC KEY-----`;

// ─── License file path ────────────────────────────────────────────────────────
const LICENSE_FILE = path.join(process.cwd(), '.license');

// ─── In-memory cache (24h TTL) ───────────────────────────────────────────────
let cachedResult: { result: LicenseValidationResult; cachedAt: number } | null = null;
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;

// ─── Hybrid check-in config ──────────────────────────────────────────────────
const CHECK_IN_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000;  // 7 days
const GRACE_PERIOD_MS = 3 * 24 * 60 * 60 * 1000;        // 3 days grace

// ─── Core: parse & verify a license key string ───────────────────────────────

/**
 * License key format: base64url(payload_json).base64url(signature_der)
 * Generated by scripts/generate-license.ts (vendor-only)
 */
export function parseLicenseKey(licenseKey: string): LicenseValidationResult {
  try {
    const parts = licenseKey.trim().split('.');
    if (parts.length !== 2) {
      return { valid: false, error: 'Invalid license key format' };
    }

    const [payloadB64, sigB64] = parts;

    // Decode payload
    const payloadJson = Buffer.from(payloadB64, 'base64url').toString('utf-8');
    const payload: LicensePayload = JSON.parse(payloadJson);

    // Verify ECDSA signature
    const verifier = crypto.createVerify('SHA256');
    verifier.update(payloadB64);
    const sigBuffer = Buffer.from(sigB64, 'base64url');
    const isSignatureValid = verifier.verify(EMBEDDED_PUBLIC_KEY, sigBuffer);

    if (!isSignatureValid) {
      return { valid: false, error: 'License signature is invalid' };
    }

    // Check expiry
    const expiresAt = new Date(payload.expiresAt);
    const now = new Date();
    if (expiresAt < now) {
      const expiredDaysAgo = Math.floor((now.getTime() - expiresAt.getTime()) / (24 * 60 * 60 * 1000));
      return { valid: false, error: `License expired ${expiredDaysAgo} day(s) ago`, payload, daysRemaining: -expiredDaysAgo };
    }

    const daysRemaining = Math.floor((expiresAt.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));

    return { valid: true, payload, daysRemaining };
  } catch (err) {
    return { valid: false, error: 'Failed to parse license key' };
  }
}

// ─── Machine check ────────────────────────────────────────────────────────────

function verifyMachineBinding(payload: LicensePayload): LicenseValidationResult {
  if (payload.edition === 'cloud') return { valid: true, payload };
  if (!payload.machineHash) return { valid: false, error: 'Local license missing machine binding', payload };

  if (!matchesMachine(payload.machineHash)) {
    return { valid: false, error: 'License is bound to a different machine', payload };
  }

  return { valid: true, payload };
}

// ─── Optional online check-in ─────────────────────────────────────────────────

async function tryOnlineCheckIn(stored: StoredLicense): Promise<void> {
  const checkInUrl = process.env.LICENSE_CHECK_IN_URL;
  if (!checkInUrl) return; // No check-in server configured

  const lastChecked = stored.lastCheckedAt ? new Date(stored.lastCheckedAt).getTime() : 0;
  if (Date.now() - lastChecked < CHECK_IN_INTERVAL_MS) return; // Not due yet

  try {
    const response = await fetch(checkInUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ licenseId: stored.payload.licenseId, customerId: stored.payload.customerId }),
      signal: AbortSignal.timeout(5000),
    });

    if (response.ok) {
      stored.lastCheckedAt = new Date().toISOString();
      saveLicenseToFile(stored);
    }
    // If server returns error (e.g. license revoked), we could handle here in future
  } catch {
    // Network unavailable — check grace period
    const daysSinceCheck = (Date.now() - lastChecked) / (24 * 60 * 60 * 1000);
    if (daysSinceCheck > (CHECK_IN_INTERVAL_MS + GRACE_PERIOD_MS) / (24 * 60 * 60 * 1000)) {
      // Exceeded grace period — but we still allow it (offline-first design)
      // Could change to: throw new Error('License check-in grace period exceeded')
    }
  }
}

// ─── File I/O ─────────────────────────────────────────────────────────────────

export function saveLicenseToFile(stored: StoredLicense): void {
  fs.writeFileSync(LICENSE_FILE, JSON.stringify(stored, null, 2), 'utf-8');
}

export function readLicenseFromFile(): StoredLicense | null {
  try {
    if (!fs.existsSync(LICENSE_FILE)) return null;
    const raw = fs.readFileSync(LICENSE_FILE, 'utf-8');
    return JSON.parse(raw) as StoredLicense;
  } catch {
    return null;
  }
}

// ─── Main exported validator ──────────────────────────────────────────────────

/**
 * Full validation: parse key → verify signature → check expiry → check machine binding.
 * Uses 24h in-memory cache. Optionally triggers background online check-in.
 */
export async function validateLicense(licenseKey: string, skipCache = false): Promise<LicenseValidationResult> {
  if (!skipCache && cachedResult && Date.now() - cachedResult.cachedAt < CACHE_TTL_MS) {
    return cachedResult.result;
  }

  const parseResult = parseLicenseKey(licenseKey);
  if (!parseResult.valid || !parseResult.payload) {
    cachedResult = { result: parseResult, cachedAt: Date.now() };
    return parseResult;
  }

  const machineResult = verifyMachineBinding(parseResult.payload);
  if (!machineResult.valid) {
    cachedResult = { result: machineResult, cachedAt: Date.now() };
    return machineResult;
  }

  const result: LicenseValidationResult = {
    valid: true,
    payload: parseResult.payload,
    daysRemaining: parseResult.daysRemaining,
  };

  cachedResult = { result, cachedAt: Date.now() };

  // Trigger optional online check-in (non-blocking, background)
  const stored = readLicenseFromFile();
  if (stored) {
    tryOnlineCheckIn(stored).catch(() => {}); // fire-and-forget
  }

  return result;
}

/**
 * Quick check: reads license from file and validates it.
 * Used on every server request via middleware.
 */
export async function checkCurrentLicense(): Promise<LicenseValidationResult> {
  // In dev mode, skip license check
  if (process.env.NODE_ENV === 'development' && process.env.SKIP_LICENSE_CHECK === 'true') {
    return { valid: true, payload: undefined, daysRemaining: 9999 };
  }

  const stored = readLicenseFromFile();
  if (!stored?.key) {
    return { valid: false, error: 'No license found' };
  }

  return validateLicense(stored.key);
}

/** Clears the in-memory cache (useful after activating a new license) */
export function clearLicenseCache(): void {
  cachedResult = null;
}
