export interface OfflineLicenseVerificationResult {
  isValid: boolean;
  reason?: string;
  payload?: any;
  error?: string;
}

function getSubtleCrypto(): SubtleCrypto | null {
  if (typeof globalThis !== 'undefined' && (globalThis as any).crypto && (globalThis as any).crypto.subtle) {
    return (globalThis as any).crypto.subtle as SubtleCrypto;
  }
  if (typeof window !== 'undefined' && (window as any).crypto && (window as any).crypto.subtle) {
    return (window as any).crypto.subtle as SubtleCrypto;
  }
  if (typeof self !== 'undefined' && (self as any).crypto && (self as any).crypto.subtle) {
    return (self as any).crypto.subtle as SubtleCrypto;
  }
  return null;
}

function base64UrlToBytes(input: string): Uint8Array {
  let str = input.replace(/-/g, '+').replace(/_/g, '/');
  while (str.length % 4 !== 0) {
    str += '=';
  }
  const binary = atob(str);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

function pemToDerBytes(pem: string): Uint8Array {
  const stripped = pem
    .replace(/-----BEGIN [^-]+-----/g, '')
    .replace(/-----END [^-]+-----/g, '')
    .replace(/\s+/g, '');
  const binary = atob(stripped);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

function bytesToString(bytes: Uint8Array): string {
  let out = '';
  for (let i = 0; i < bytes.length; i++) {
    out += String.fromCharCode(bytes[i]);
  }
  return out;
}

export async function verifyLicenseOffline(
  signedToken: string,
  publicKeyPem: string
): Promise<OfflineLicenseVerificationResult> {
  try {
    const subtle = getSubtleCrypto();
    if (!subtle) {
      return { isValid: false, reason: 'UNSUPPORTED_ENVIRONMENT' };
    }

    if (typeof signedToken !== 'string' || signedToken.length === 0) {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    const segments = signedToken.split('.');
    if (segments.length !== 3) {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    const [headerB64, payloadB64, signatureB64] = segments;

    let headerBytes: Uint8Array;
    let signatureBytes: Uint8Array;
    try {
      headerBytes = base64UrlToBytes(headerB64);
      signatureBytes = base64UrlToBytes(signatureB64);
    } catch {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    let headerJson: any;
    try {
      headerJson = JSON.parse(bytesToString(headerBytes));
    } catch {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    if (!headerJson || headerJson.alg !== 'EdDSA') {
      return { isValid: false, reason: 'UNSUPPORTED_ALGORITHM' };
    }

    let derBytes: Uint8Array;
    try {
      derBytes = pemToDerBytes(publicKeyPem);
    } catch {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    let cryptoKey: CryptoKey;
    try {
      cryptoKey = await subtle.importKey(
        'spki',
        derBytes,
        { name: 'Ed25519' },
        false,
        ['verify']
      );
    } catch {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    const dataBytes = new TextEncoder().encode(`${headerB64}.${payloadB64}`);

    let signatureOk = false;
    try {
      signatureOk = await subtle.verify(
        { name: 'Ed25519' },
        cryptoKey,
        signatureBytes,
        dataBytes
      );
    } catch {
      return { isValid: false, reason: 'INVALID_SIGNATURE' };
    }

    if (!signatureOk) {
      return { isValid: false, reason: 'INVALID_SIGNATURE' };
    }

    let payloadJson: any;
    try {
      const payloadBytes = base64UrlToBytes(payloadB64);
      payloadJson = JSON.parse(bytesToString(payloadBytes));
    } catch {
      return { isValid: false, reason: 'INVALID_FORMAT' };
    }

    const nowSeconds = Math.floor(Date.now() / 1000);

    if (typeof payloadJson?.exp === 'number' && nowSeconds > payloadJson.exp) {
      return { isValid: false, reason: 'EXPIRED', payload: payloadJson };
    }
    if (typeof payloadJson?.nbf === 'number' && nowSeconds < payloadJson.nbf) {
      return { isValid: false, reason: 'NOT_YET_VALID', payload: payloadJson };
    }

    return { isValid: true, payload: payloadJson };
  } catch (e: any) {
    return { isValid: false, reason: 'INVALID_FORMAT', error: e?.message ?? String(e) };
  }
}
