import { DEFAULT_LICENSE, DEFAULT_LICENSE_RESOLUTION } from './license.defaults';
import { fetchStandardLicensePublicKey } from './license.contract';
import { isLicenseUsable } from './feature-gates';
import { deactivateLicenseByHttp, refreshLicenseByHttp, resolveLicenseByHttp } from './http';
import { normalizeLicense } from './license.normalize';
import { clearLicenseCache, loadLicenseCache, saveLicenseCache } from './storage';
import { normalizeSignedLicenseInput } from './token';
import { verifySignedLicensePayload } from './verifier';
import type {
  AsukaLicenseResolutionResult,
  AsukaLicenseResolver,
  AsukaLicenseVerifier,
  AsukaLicenseDeactivateContext,
  AsukaLicenseHttpOptions,
  AsukaLicenseResolveContext,
  AsukaLicenseTokenInput,
  AsukaSignedLicensePayload,
} from './license.types';

export interface LicenseManagerOptions {
  storageKey?: string;
  resolver?: AsukaLicenseResolver;
  verifier?: AsukaLicenseVerifier;
  publicKey?: string;
  audience?: string;
  version?: string;
  http?: AsukaLicenseHttpOptions;
}

export interface LicenseManagerInitInput {
  token?: AsukaLicenseTokenInput;
  key?: string;
  domain?: string;
  instanceId?: string;
}

const LICENSE_DEBUG_PREFIX = '[AsukaDesigner:license:debug]';

function logLicenseDebug(message: string, details?: unknown): void {
  try {
    if ((globalThis as any).__ASUKA_LICENSE_DEBUG__ !== true) return;
    if (typeof details === 'undefined') console.debug(LICENSE_DEBUG_PREFIX, message);
    else console.debug(LICENSE_DEBUG_PREFIX, message, details);
  } catch {}
}

function toDebugError(error: unknown): Record<string, unknown> {
  if (error instanceof Error) {
    return {
      name: error.name,
      message: error.message,
      stack: error.stack,
    };
  }
  return { value: String(error) };
}

export class LicenseManager {
  private readonly options: LicenseManagerOptions;
  private current: AsukaLicenseResolutionResult = DEFAULT_LICENSE_RESOLUTION;
  private currentSigned?: AsukaSignedLicensePayload;
  private resolvedPublicKey?: string;
  private publicKeyPromise?: Promise<string | undefined>;

  constructor(options: LicenseManagerOptions = {}) {
    this.options = options;
  }

  getState(): AsukaLicenseResolutionResult {
    return this.current;
  }

  clearCache(): void {
    this.currentSigned = undefined;
    if (this.options.storageKey) clearLicenseCache(this.options.storageKey);
  }

  hasRefreshToken(): boolean {
    return !!this.getCurrentCompactToken();
  }

  getCurrentCompactToken(): string | undefined {
    if (typeof this.currentSigned?.compact === 'string' && this.currentSigned.compact.trim()) {
      return this.currentSigned.compact.trim();
    }
    return undefined;
  }

  private async getEffectivePublicKey(): Promise<string | undefined> {
    const explicit = typeof this.options.publicKey === 'string' ? this.options.publicKey.trim() : '';
    if (explicit) return explicit;
    if (this.resolvedPublicKey) return this.resolvedPublicKey;
    if (typeof this.options.verifier === 'function') return undefined;
    if (!this.options.http) return undefined;
    if (!this.publicKeyPromise) {
      logLicenseDebug('Fetching public key from the official API');
      this.publicKeyPromise = fetchStandardLicensePublicKey({
        headers: this.options.http?.headers,
        credentials: this.options.http?.credentials,
      })
        .then((data) => {
          const key = typeof data.publicKey === 'string' ? data.publicKey.trim() : '';
          this.resolvedPublicKey = key || undefined;
          logLicenseDebug('Resolved public key response', {
            hasKey: !!this.resolvedPublicKey,
            keyLength: this.resolvedPublicKey?.length ?? 0,
            alg: (data as any)?.alg,
            kid: (data as any)?.kid,
          });
          return this.resolvedPublicKey;
        })
        .catch((error) => {
          logLicenseDebug('Failed to fetch public key', { error: error instanceof Error ? error.message : String(error) });
          return undefined;
        })
        .finally(() => {
          this.publicKeyPromise = undefined;
        });
    }
    return await this.publicKeyPromise;
  }

  async init(input: LicenseManagerInitInput = {}): Promise<AsukaLicenseResolutionResult> {
    logLicenseDebug('LicenseManager.init() start', {
      hasTokenInput: !!input.token,
      hasKeyInput: !!input.key,
      domain: input.domain,
      instanceId: input.instanceId,
      storageKey: this.options.storageKey ?? null,
      hasCurrentSigned: !!this.currentSigned,
      hasCurrentCompactToken: !!this.getCurrentCompactToken(),
    });

    if (input.token) {
      logLicenseDebug('LicenseManager.init() using explicit token input');
      const signed = normalizeSignedLicenseInput(input.token);
      const result = await this.resolveFromSignedPayload(signed, 'token', input);
      this.current = result;
      if (result.signatureValid && this.options.storageKey) saveLicenseCache(this.options.storageKey, signed);
      else if (result.signatureValid) logLicenseDebug('LicenseManager.init() persistence disabled; not saving explicit token');
      if (result.signatureValid) this.currentSigned = signed;
      logLicenseDebug('LicenseManager.init() explicit token result', {
        ok: result.ok,
        signatureValid: result.signatureValid,
        plan: result.payload?.plan,
        status: result.payload?.status,
        active: result.payload?.active,
        source: result.source,
      });
      return result;
    }

    if (input.key) {
      logLicenseDebug('LicenseManager.init() resolving by key', {
        domain: input.domain,
        instanceId: input.instanceId,
      });
      try {
        const signed = await this.resolveByKey({
          key: input.key,
          domain: input.domain,
          instanceId: input.instanceId,
          version: this.options.version,
        });
        logLicenseDebug('LicenseManager.init() resolveByKey() returned signed payload', {
          format: signed?.format,
          alg: signed?.alg,
          kid: signed?.kid,
          hasCompact: !!signed?.compact,
          hasSignature: !!signed?.signature,
        });
        const result = await this.resolveFromSignedPayload(signed, 'key', input);
        this.current = result;
        if (result.signatureValid && this.options.storageKey) saveLicenseCache(this.options.storageKey, signed);
        else if (result.signatureValid) logLicenseDebug('LicenseManager.init() persistence disabled; not saving resolved key token');
        if (result.signatureValid) this.currentSigned = signed;
        logLicenseDebug('LicenseManager.init() key result', {
          ok: result.ok,
          signatureValid: result.signatureValid,
          plan: result.payload?.plan,
          status: result.payload?.status,
          active: result.payload?.active,
          source: result.source,
        });
        return result;
      } catch (error) {
        logLicenseDebug('LicenseManager.init() resolveByKey() failed; falling through to cache/default', toDebugError(error));
      }
    }

    if (this.options.storageKey) {
      const cached = loadLicenseCache(this.options.storageKey);
      logLicenseDebug('LicenseManager.init() cache lookup result', {
        hasCache: !!cached,
        hasCachedSigned: !!cached?.signed,
      });
      if (cached?.signed) {
        try {
          const result = await this.resolveFromSignedPayload(cached.signed, 'cache', input);
          this.current = result;
          if (result.signatureValid) this.currentSigned = cached.signed;
          logLicenseDebug('LicenseManager.init() cache result', {
            ok: result.ok,
            signatureValid: result.signatureValid,
            plan: result.payload?.plan,
            status: result.payload?.status,
            active: result.payload?.active,
            source: result.source,
          });
          return result;
        } catch (error) {
          logLicenseDebug('LicenseManager.init() cached signed payload failed; falling through to default', toDebugError(error));
        }
      }
    } else {
      logLicenseDebug('LicenseManager.init() persistent cache disabled; skipping cache lookup');
    }

    this.currentSigned = undefined;
    this.current = {
      ...DEFAULT_LICENSE_RESOLUTION,
      payload: DEFAULT_LICENSE,
      resolvedAt: new Date().toISOString(),
    };
    logLicenseDebug('LicenseManager.init() returning DEFAULT_LICENSE_RESOLUTION fallback', {
      reason: 'no-valid-token-key-or-cache-disabled',
      payloadPlan: this.current.payload?.plan,
      payloadStatus: this.current.payload?.status,
      payloadActive: this.current.payload?.active,
    });
    return this.current;
  }

  async refresh(input: LicenseManagerInitInput = {}): Promise<AsukaLicenseResolutionResult> {
    logLicenseDebug('LicenseManager.refresh() start', {
      hasHttp: !!this.options.http,
      hasExplicitToken: typeof input.token === 'string' && !!input.token.trim(),
      hasCurrentCompactToken: !!this.getCurrentCompactToken(),
      domain: input.domain,
      instanceId: input.instanceId,
    });
    if (!this.options.http) {
      logLicenseDebug('LicenseManager.refresh() no HTTP options configured; delegating to init()');
      return await this.init(input);
    }

    const explicitToken = typeof input.token === 'string' ? input.token.trim() : undefined;
    const currentToken = explicitToken || this.getCurrentCompactToken();

    if (!currentToken) {
      logLicenseDebug('LicenseManager.refresh() no token available; delegating to init()', {
        hasExplicitToken: !!explicitToken,
        hasCurrentCompactToken: !!this.getCurrentCompactToken(),
      });
      return await this.init(input);
    }

    try {
      const signed = await refreshLicenseByHttp(this.options.http, {
        token: currentToken,
        domain: input.domain,
        instanceId: input.instanceId,
        version: this.options.version,
      });
      logLicenseDebug('LicenseManager.refresh() refreshLicenseByHttp() returned signed payload', {
        format: signed?.format,
        alg: signed?.alg,
        kid: signed?.kid,
        hasCompact: !!signed?.compact,
      });
      const result = await this.resolveFromSignedPayload(signed, explicitToken ? 'token' : 'cache', input);
      this.current = result;
      if (result.signatureValid) this.currentSigned = signed;
      if (result.signatureValid && this.options.storageKey) saveLicenseCache(this.options.storageKey, signed);
      else if (result.signatureValid) logLicenseDebug('LicenseManager.refresh() persistence disabled; not saving refreshed token');
      logLicenseDebug('LicenseManager.refresh() result', {
        ok: result.ok,
        signatureValid: result.signatureValid,
        plan: result.payload?.plan,
        status: result.payload?.status,
        active: result.payload?.active,
      });
      return result;
    } catch (error) {
      logLicenseDebug('LicenseManager.refresh() failed; delegating to init()', toDebugError(error));
      return await this.init(input);
    }
  }

  async deactivate(input: AsukaLicenseDeactivateContext = {}): Promise<boolean> {
    if (!this.options.http) return false;
    try {
      const ok = await deactivateLicenseByHttp(this.options.http, {
        key: input.key,
        domain: input.domain,
        instanceId: input.instanceId,
        version: input.version ?? this.options.version,
      });
      if (ok) {
        this.currentSigned = undefined;
        this.clearCache();
        this.current = {
          ...DEFAULT_LICENSE_RESOLUTION,
          payload: DEFAULT_LICENSE,
          resolvedAt: new Date().toISOString(),
        };
      }
      return ok;
    } catch {
      return false;
    }
  }

  private async resolveByKey(input: AsukaLicenseResolveContext): Promise<AsukaSignedLicensePayload> {
    if (typeof this.options.resolver === 'function') {
      return normalizeSignedLicenseInput(await this.options.resolver(input));
    }
    if (this.options.http) {
      return await resolveLicenseByHttp(this.options.http, input);
    }
    throw new Error('No license resolver configured');
  }

  private async resolveFromSignedPayload(
    signed: AsukaSignedLicensePayload,
    source: AsukaLicenseResolutionResult['source'],
    expectations: Pick<LicenseManagerInitInput, 'domain' | 'instanceId'> = {},
  ): Promise<AsukaLicenseResolutionResult> {
    const normalized = normalizeSignedLicenseInput(signed);
    this.currentSigned = normalized;
    const publicKey = await this.getEffectivePublicKey();
    logLicenseDebug('Resolving signed payload', {
      source,
      format: normalized?.format,
      alg: normalized?.alg,
      kid: normalized?.kid,
      hasCompact: !!normalized?.compact,
      hasSigningInput: !!normalized?.signingInput,
      hasSignature: !!normalized?.signature,
      hasPublicKey: !!publicKey,
      publicKeyLength: publicKey?.length ?? 0,
      expectedAudience: this.options.audience ?? this.options.http?.audience ?? 'asuka-designer',
      domain: expectations.domain,
      instanceId: expectations.instanceId,
    });
    const signatureValid = await verifySignedLicensePayload({
      signed: normalized,
      publicKey,
      verifier: this.options.verifier,
      expectedAudience: this.options.audience ?? this.options.http?.audience ?? 'asuka-designer',
      domain: expectations.domain,
      instanceId: expectations.instanceId,
    });
    logLicenseDebug('Resolved signed payload verification complete', {
      source,
      signatureValid,
      verifierPayloadPlan: (normalized?.payload as any)?.plan,
      verifierPayloadStatus: (normalized?.payload as any)?.status,
      verifierPayloadActive: (normalized?.payload as any)?.active,
    });
    const payload = normalizeLicense(normalized?.payload);
    logLicenseDebug('Resolved signed payload normalized payload', {
      source,
      normalizedPlan: (payload as any)?.plan,
      normalizedStatus: (payload as any)?.status,
      normalizedActive: (payload as any)?.active,
      normalizedDomain: (payload as any)?.domain,
      normalizedInstanceId: (payload as any)?.instanceId,
      features: Object.keys((payload as any)?.features ?? {}),
    });
    const usable = isLicenseUsable(payload);
    const ok = signatureValid && usable;

    logLicenseDebug('Resolved signed payload result', {
      source,
      ok,
      signatureValid,
      plan: (payload as any)?.plan,
      status: (payload as any)?.status,
      active: (payload as any)?.active,
      usable,
      payloadDomain: (payload as any)?.domain,
      payloadInstanceId: (payload as any)?.instanceId,
    });

    return {
      ok,
      source,
      payload,
      signatureValid,
      resolvedAt: new Date().toISOString(),
    };
  }
}
