import type { AsukaLicenseEntitlements } from './license/license.types';
import { getCommercialQuotaState } from './license/commercial-limits';
import { emitAfter, emitBefore } from './events';
import { buildAsukaApiEndpoint } from './apiEndpoints';

export interface AsukaPaidExportConsumeInput {
  type?: string;
  amount?: number;
  reason?: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaPaidExportConsumeResult {
  ok?: boolean;
  billable?: boolean;
  exportType?: string;
  metadata?: Record<string, unknown>;
  usage?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaPaidExportConsumeConfig {
  headers?: Record<string, string>;
  credentials?: RequestCredentials;
  handler?: (input: AsukaPaidExportConsumeInput & { token?: string | null; licenseToken?: string | null }) => Promise<AsukaPaidExportConsumeResult>;
  creditCost?: number;
}

export interface AsukaServerHdExportInput {
  target?: Record<string, unknown>;
  options?: Record<string, unknown>;
  document?: unknown;
  state?: unknown;
  metadata?: Record<string, unknown>;
}

export interface AsukaServerHdExportPayload extends AsukaServerHdExportInput {
  renderPayloadVersion: 1;
  source: 'asuka-designer-js';
  target: Record<string, unknown>;
  options: Record<string, unknown>;
  metadata: Record<string, unknown>;
  token?: string | null;
  licenseToken?: string | null;
}

export interface AsukaServerHdExportResult {
  ok?: boolean;
  url?: string;
  downloadUrl?: string;
  fileUrl?: string;
  dataUrl?: string;
  blob?: Blob;
  filename?: string;
  mimeType?: string;
  metadata?: Record<string, unknown>;
  usage?: Record<string, unknown>;
  [key: string]: unknown;
}

export interface AsukaServerHdExportConfig {
  headers?: Record<string, string>;
  credentials?: RequestCredentials;
  handler?: (payload: AsukaServerHdExportPayload) => Promise<AsukaServerHdExportResult>;
  creditCost?: number;
  autoDownload?: boolean;
  defaults?: Record<string, unknown>;
}

export interface AsukaExportsOptions {
  consume?: AsukaPaidExportConsumeConfig;
  serverHd?: AsukaServerHdExportConfig;
}

export interface AsukaExportRuntimeDeps {
  options?: AsukaExportsOptions | null;
  canUseFeature: (feature: string) => boolean;
  getLicense: () => AsukaLicenseEntitlements | null | undefined;
  getLicenseToken?: () => unknown;
  patchLicense?: (patch: Partial<AsukaLicenseEntitlements>) => void;
  exportState?: () => { state?: unknown; document?: unknown };
  getDefaultTarget?: () => Record<string, unknown>;
}

type ResolvedAsukaPaidExportConsumeConfig = AsukaPaidExportConsumeConfig & { endpoint: string };
type ResolvedAsukaServerHdExportConfig = AsukaServerHdExportConfig & { endpoint: string };

function toNumber(value: unknown): number | null {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  }
  return null;
}

function compactLicenseToken(input: unknown): string | null {
  if (typeof input === 'string' && input.trim()) return input.trim();
  if (input && typeof input === 'object') {
    const record = input as Record<string, unknown>;
    if (typeof record.compact === 'string' && record.compact.trim()) return record.compact.trim();
    if (typeof record.token === 'string' && record.token.trim()) return record.token.trim();
    if (typeof record.licenseToken === 'string' && record.licenseToken.trim()) return record.licenseToken.trim();
  }
  return null;
}

function computeRemaining(limit: number | null, used: number): number | null {
  if (limit === null || limit <= 0) return null;
  return Math.max(0, limit - used);
}

async function readExportResponse(response: Response): Promise<any> {
  const contentType = response.headers.get('content-type') || '';
  if (contentType.includes('application/json')) return await response.json();
  if (contentType.startsWith('image/') || contentType.includes('application/pdf') || contentType.includes('application/octet-stream')) {
    const blob = await response.blob();
    const disposition = response.headers.get('content-disposition') || '';
    const filenameMatch = disposition.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);
    const filename = filenameMatch ? decodeURIComponent(filenameMatch[1].replace(/"/g, '').trim()) : undefined;
    return { ok: response.ok, blob, mimeType: blob.type || contentType, filename };
  }
  const text = await response.text();
  try { return JSON.parse(text); } catch { return { ok: response.ok, dataUrl: text }; }
}

function getUsagePayload(result: any): Record<string, unknown> | null {
  const metadataUsage = result?.metadata && typeof result.metadata === 'object' ? (result.metadata as any).usage : null;
  const directUsage = result?.usage;
  if (metadataUsage && typeof metadataUsage === 'object') return metadataUsage as Record<string, unknown>;
  if (directUsage && typeof directUsage === 'object') return directUsage as Record<string, unknown>;
  return null;
}

function shouldAutoDownload(configValue: boolean | undefined, inputValue: unknown): boolean {
  if (typeof inputValue === 'boolean') return inputValue;
  return configValue !== false;
}

function readNestedString(input: unknown, keys: string[]): string {
  if (!input || typeof input !== 'object') return '';
  const record = input as Record<string, unknown>;
  for (const key of keys) {
    const value = record[key];
    if (typeof value === 'string' && value.trim()) return value.trim();
  }
  return '';
}

function getDownloadUrl(result: AsukaServerHdExportResult): string | null {
  const direct = String(result.downloadUrl || result.url || result.fileUrl || result.dataUrl || '').trim();
  if (direct) return direct;
  const nested = readNestedString((result as any)?.file, ['downloadUrl', 'url', 'fileUrl', 'dataUrl'])
    || readNestedString((result as any)?.output, ['downloadUrl', 'url', 'fileUrl', 'dataUrl'])
    || readNestedString((result as any)?.result, ['downloadUrl', 'url', 'fileUrl', 'dataUrl']);
  if (nested) return nested;
  if (typeof (result as any)?.output === 'string' && String((result as any).output).trim()) return String((result as any).output).trim();
  if (typeof (result as any)?.result === 'string' && String((result as any).result).trim()) return String((result as any).result).trim();
  return null;
}

function extensionFromMime(mimeType: string | undefined, fallback = 'png'): string {
  const mime = String(mimeType || '').toLowerCase();
  if (mime.includes('jpeg')) return 'jpg';
  if (mime.includes('webp')) return 'webp';
  if (mime.includes('pdf')) return 'pdf';
  if (mime.includes('svg')) return 'svg';
  if (mime.includes('json')) return 'json';
  return fallback;
}

function normalizeBase64Download(result: AsukaServerHdExportResult): AsukaServerHdExportResult {
  if (result.blob || result.dataUrl) return result;
  const payload = result as any;
  const base64 = String(payload.base64 || payload.fileBase64 || payload.contentBase64 || '').trim();
  if (!base64) return result;
  const mimeType = String(result.mimeType || payload.mime || payload.contentType || 'image/png');
  return {
    ...result,
    mimeType,
    dataUrl: base64.startsWith('data:') ? base64 : `data:${mimeType};base64,${base64}`,
  };
}

function triggerDownload(result: AsukaServerHdExportResult, fallbackName = 'asuka-export'): boolean {
  if (typeof document === 'undefined') return false;
  const normalized = normalizeBase64Download(result);
  const anchor = document.createElement('a');
  let revokeUrl: string | null = null;
  if (normalized.blob) {
    revokeUrl = URL.createObjectURL(normalized.blob);
    anchor.href = revokeUrl;
  } else {
    const url = getDownloadUrl(normalized);
    if (!url) return false;
    anchor.href = url;
  }
  const fallbackExtension = extensionFromMime(normalized.mimeType, 'png');
  anchor.download = normalized.filename || `${fallbackName}.${fallbackExtension}`;
  anchor.rel = 'noopener';
  anchor.style.display = 'none';
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
  if (revokeUrl) setTimeout(() => URL.revokeObjectURL(revokeUrl!), 1500);
  return true;
}


export const DEFAULT_ASUKA_EXPORT_ENDPOINTS = {
  consume: buildAsukaApiEndpoint('/v1/exports/consume'),
  serverHd: buildAsukaApiEndpoint('/v1/exports/server-hd'),
} as const;

const DEFAULT_PAID_EXPORT_CONSUME_CONFIG: Required<Pick<ResolvedAsukaPaidExportConsumeConfig, 'endpoint' | 'creditCost'>> = {
  endpoint: DEFAULT_ASUKA_EXPORT_ENDPOINTS.consume,
  creditCost: 1,
};

const DEFAULT_SERVER_HD_EXPORT_CONFIG: Required<Pick<ResolvedAsukaServerHdExportConfig, 'endpoint' | 'creditCost' | 'autoDownload'>> = {
  endpoint: DEFAULT_ASUKA_EXPORT_ENDPOINTS.serverHd,
  creditCost: 1,
  autoDownload: true,
};

export function createAsukaExportRuntime(deps: AsukaExportRuntimeDeps) {
  const options: {
    consume: ResolvedAsukaPaidExportConsumeConfig;
    serverHd: ResolvedAsukaServerHdExportConfig;
  } = {
    consume: {
      ...DEFAULT_PAID_EXPORT_CONSUME_CONFIG,
      ...(deps.options?.consume ?? {}),
      endpoint: DEFAULT_ASUKA_EXPORT_ENDPOINTS.consume,
    },
    serverHd: {
      ...DEFAULT_SERVER_HD_EXPORT_CONFIG,
      ...(deps.options?.serverHd ?? {}),
      endpoint: DEFAULT_ASUKA_EXPORT_ENDPOINTS.serverHd,
    },
  };

  const getQuota = () => getCommercialQuotaState(deps.getLicense(), 'exports');

  const ensureExportCredits = (amount = 1, action = 'Paid export') => {
    const quota = getQuota();
    if (quota.remaining !== null && quota.remaining < amount) {
      throw new Error(`[AsukaDesigner] Not enough paid export credits remaining for ${action}.`);
    }
    return quota;
  };

  const patchUsage = (usage: Record<string, unknown>): boolean => {
    const used = toNumber(usage.exportsUsed);
    const remaining = toNumber(usage.exportsRemaining);
    if (used === null && remaining === null) return false;
    const license = deps.getLicense();
    const prevUsage = (license?.usage ?? {}) as Record<string, unknown>;
    deps.patchLicense?.({
      usage: {
        ...prevUsage,
        ...usage,
        ...(used !== null ? { exportsUsed: used } : {}),
        ...(remaining !== null ? { exportsRemaining: remaining } : {}),
      },
    } as Partial<AsukaLicenseEntitlements>);
    emitAfter('exports.usage.synced', { usage });
    return true;
  };

  const consumeCreditsLocally = (amount = 1, reason = 'Paid export') => {
    if (amount <= 0) return getQuota();
    const quota = ensureExportCredits(amount, reason);
    const license = deps.getLicense();
    const prevUsage = (license?.usage ?? {}) as Record<string, unknown>;
    const used = (toNumber(prevUsage.exportsUsed) ?? 0) + amount;
    const remaining = computeRemaining(quota.limit, used);
    deps.patchLicense?.({
      usage: {
        ...prevUsage,
        exportsUsed: used,
        ...(remaining !== null ? { exportsRemaining: remaining } : {}),
      },
    } as Partial<AsukaLicenseEntitlements>);
    emitAfter('exports.credits.consumed', { amount, reason, used, remaining });
    return getQuota();
  };

  const consumePaidExport = async (input: AsukaPaidExportConsumeInput = {}) => {
    const consume = options.consume ?? {};
    const amount = Math.max(0, Number(input.amount ?? consume.creditCost ?? 1) || 0);
    const reason = String(input.reason || `${input.type || 'paid'}_export`);
    ensureExportCredits(amount, reason);
    const token = compactLicenseToken(deps.getLicenseToken?.());
    const payload = {
      type: input.type || 'paid',
      amount,
      reason,
      metadata: input.metadata ?? {},
      token,
      licenseToken: token,
    };
    emitBefore('exports.consumePaidExport', { payload: { ...payload, token: token ? '[token]' : null, licenseToken: token ? '[token]' : null } });

    let result: AsukaPaidExportConsumeResult | null = null;
    if (consume.handler) {
      result = await consume.handler(payload);
    } else if (consume.endpoint) {
      const response = await fetch(consume.endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(token ? { Authorization: `Bearer ${token}`, 'X-License-Token': token } : {}),
          ...(consume.headers ?? {}),
        },
        credentials: consume.credentials,
        body: JSON.stringify(payload),
      });
      result = await readExportResponse(response);
      if (!response.ok || result?.ok === false) {
        throw new Error(String((result as any)?.error || (result as any)?.message || `Paid export consumption failed with HTTP ${response.status}.`));
      }
    }

    const synced = result ? patchUsage(getUsagePayload(result) ?? {}) : false;
    if (!synced) consumeCreditsLocally(amount, reason);
    const finalResult = result ?? { ok: true, billable: amount > 0, exportType: payload.type };
    emitAfter('exports.consumePaidExport', { result: finalResult });
    return finalResult;
  };

  const buildServerHdPayload = (input: AsukaServerHdExportInput = {}): AsukaServerHdExportPayload => {
    const serverHd = options.serverHd ?? {};
    const exported = deps.exportState?.() ?? {};
    const normalizedTarget = {
      ...(deps.getDefaultTarget?.() ?? {}),
      ...(input.target ?? {}),
    };
    const normalizedOptions = {
      format: 'png',
      dpi: 300,
      scale: 4,
      ...(serverHd.defaults ?? {}),
      ...(input.options ?? {}),
    };
    const cost = Math.max(0, Number((input.metadata as any)?.paidExportCost ?? serverHd.creditCost ?? 1) || 0);
    const compactToken = compactLicenseToken(deps.getLicenseToken?.());
    return {
      renderPayloadVersion: 1,
      source: 'asuka-designer-js',
      target: normalizedTarget,
      options: normalizedOptions,
      document: input.document ?? exported.document,
      state: input.state ?? exported.state,
      metadata: {
        ...(input.metadata ?? {}),
        paidExportCost: cost,
      },
      token: compactToken,
      licenseToken: compactToken,
    };
  };

  const serverHdExport = async (input: AsukaServerHdExportInput = {}) => {
    if (!deps.canUseFeature('serverHdExport')) {
      throw new Error('[AsukaDesigner] Server-side HD export requires the serverHdExport entitlement.');
    }
    const serverHd = options.serverHd ?? {};
    if (!serverHd.handler && !serverHd.endpoint) {
      throw new Error('[AsukaDesigner] The official server-side HD export service is unavailable.');
    }
    const payload = buildServerHdPayload(input);
    const amount = Math.max(0, Number((payload.metadata as any)?.paidExportCost ?? serverHd.creditCost ?? 1) || 0);
    ensureExportCredits(amount, 'server-side HD export');
    emitBefore('exports.serverHd.export', { payload: { ...payload, token: payload.token ? '[token]' : null, licenseToken: payload.licenseToken ? '[token]' : null } });

    let result: AsukaServerHdExportResult;
    if (serverHd.handler) {
      result = await serverHd.handler(payload);
    } else {
      const response = await fetch(serverHd.endpoint!, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(payload.token ? { Authorization: `Bearer ${payload.token}`, 'X-License-Token': payload.token } : {}),
          ...(serverHd.headers ?? {}),
        },
        credentials: serverHd.credentials,
        body: JSON.stringify(payload),
      });
      result = await readExportResponse(response);
      if (!response.ok || result?.ok === false) {
        throw new Error(String((result as any)?.error || (result as any)?.message || `Server-side HD export failed with HTTP ${response.status}.`));
      }
    }

    const synced = patchUsage(getUsagePayload(result) ?? {});
    if (!synced) consumeCreditsLocally(amount, 'server_hd_export');
    if (shouldAutoDownload(serverHd.autoDownload, (input.options as any)?.autoDownload)) {
      const downloaded = triggerDownload(result, String((payload.options as any)?.name || 'asuka-export'));
      if (!downloaded) {
        throw new Error('[AsukaDesigner] Server-side HD export completed but did not return a downloadable file or URL.');
      }
    }
    emitAfter('exports.serverHd.export', { result: { ...result, blob: result.blob ? '[binary]' : undefined } });
    return result;
  };

  return {
    getQuota,
    consumeCredits: consumeCreditsLocally,
    consumePaidExport,
    buildServerHdPayload,
    serverHd: {
      export: serverHdExport,
    },
  };
}

export type AsukaExportApi = ReturnType<typeof createAsukaExportRuntime>;
