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

export interface AsukaAiTextToImageInput {
  prompt: string;
  negativePrompt?: string;
  aspectRatio?: string;
  width?: number;
  height?: number;
  model?: string;
  stylePreset?: string;
  seed?: number;
  responseFormat?: 'url' | 'dataUrl' | 'blob';
  metadata?: Record<string, unknown>;
}

export interface AsukaAiTextToImageResult {
  url?: string;
  dataUrl?: string;
  blob?: Blob;
  revisedPrompt?: string;
  metadata?: Record<string, unknown>;
  creditsUsed?: number;
}

export interface AsukaAiTextToImageConfig {
  headers?: Record<string, string>;
  credentials?: RequestCredentials;
  handler?: (input: AsukaAiTextToImageInput) => Promise<AsukaAiTextToImageResult>;
  creditCost?: number;
  defaultAspectRatio?: string;
  defaultModel?: string;
  defaultStylePreset?: string;
}

export type AsukaAiBackgroundRemovalConfig = Omit<AsukaBackgroundRemovalConfig, 'endpoint'> & {
  creditCost?: number;
};

export interface AsukaAiOptions {
  textToImage?: AsukaAiTextToImageConfig;
  backgroundRemoval?: AsukaAiBackgroundRemovalConfig;
}

export const DEFAULT_ASUKA_AI_ENDPOINTS = {
  textToImage: buildAsukaApiEndpoint('/v1/ai/text-to-image'),
  backgroundRemoval: buildAsukaApiEndpoint('/v1/ai/background-removal'),
} as const;

export interface AsukaAiRuntimeDeps {
  options?: AsukaAiOptions | null;
  canUseFeature: (feature: string) => boolean;
  getLicense: () => AsukaLicenseEntitlements | null | undefined;
  getLicenseToken?: () => unknown;
  patchLicense?: (patch: Partial<AsukaLicenseEntitlements>) => void;
  fallbackBackgroundRemoval?: ((input: AsukaBackgroundRemovalInput) => Promise<AsukaBackgroundRemovalResult>) | null;
}

type ResolvedAsukaAiTextToImageConfig = AsukaAiTextToImageConfig & { endpoint: string };
type ResolvedAsukaAiBackgroundRemovalConfig = AsukaAiBackgroundRemovalConfig & { 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 getCompactLicenseToken(input: unknown): string | null {
  if (!input) return null;
  if (typeof input === 'string' && input.trim()) return input.trim();
  if (typeof input === 'object') {
    const compact = (input as any).compact;
    if (typeof compact === 'string' && compact.trim()) return compact.trim();
    const token = (input as any).token;
    if (typeof token === 'string' && token.trim()) return token.trim();
  }
  return null;
}

async function readAiResponse(response: Response): Promise<any> {
  const contentType = response.headers.get('content-type') || '';
  if (contentType.includes('application/json')) return await response.json();
  if (contentType.startsWith('image/')) return { blob: await response.blob() };
  const text = await response.text();
  try { return JSON.parse(text); } catch { return { dataUrl: text }; }
}

function getAiResponseError(payload: any, fallback: string): Error {
  const errorPayload = payload?.error;
  const message = typeof errorPayload?.message === 'string' && errorPayload.message.trim()
    ? errorPayload.message.trim()
    : typeof errorPayload === 'string' && errorPayload.trim()
      ? errorPayload.trim()
      : fallback;
  const error = new Error(`[AsukaDesigner] ${message}`);
  if (typeof errorPayload?.code === 'string' && errorPayload.code.trim()) {
    (error as any).code = errorPayload.code.trim();
  }
  return error;
}

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

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

function applyServerUsage(
  deps: AsukaAiRuntimeDeps,
  usage: Record<string, unknown> | null | undefined,
): boolean {
  if (!usage || typeof usage !== 'object') return false;
  const current = deps.getLicense();
  const prevUsage = (current?.usage ?? {}) as Record<string, unknown>;
  deps.patchLicense?.({
    usage: {
      ...prevUsage,
      ...usage,
    },
  } as Partial<AsukaLicenseEntitlements>);
  return true;
}

function buildAiAuthHeaders(
  baseHeaders: Record<string, string> | undefined,
  token: string | null,
): Record<string, string> {
  const headers: Record<string, string> = { ...(baseHeaders ?? {}) };
  if (token) {
    headers.Authorization = headers.Authorization || `Bearer ${token}`;
    headers['X-License-Token'] = headers['X-License-Token'] || token;
  }
  return headers;
}

export function createAsukaAiRuntime(deps: AsukaAiRuntimeDeps) {
  const options: {
    textToImage: ResolvedAsukaAiTextToImageConfig;
    backgroundRemoval: ResolvedAsukaAiBackgroundRemovalConfig;
  } = {
    textToImage: {
      creditCost: 1,
      ...(deps.options?.textToImage ?? {}),
      endpoint: DEFAULT_ASUKA_AI_ENDPOINTS.textToImage,
    },
    backgroundRemoval: {
      creditCost: 1,
      ...(deps.options?.backgroundRemoval ?? {}),
      endpoint: DEFAULT_ASUKA_AI_ENDPOINTS.backgroundRemoval,
    },
  };

  const ensureAiCredits = (amount = 1, action = 'Using AI tools') => {
    if (!deps.canUseFeature('aiCredits')) {
      throw new Error(`[AsukaDesigner] ${action} requires the aiCredits entitlement.`);
    }
    const quota = getCommercialQuotaState(deps.getLicense(), 'aiCredits');
    if (quota.remaining !== null && quota.remaining < amount) {
      throw new Error('[AsukaDesigner] Not enough AI credits remaining for this action.');
    }
    return quota;
  };

  const consumeAiCredits = (amount = 1, reason = 'AI operation') => {
    if (amount <= 0) return getCommercialQuotaState(deps.getLicense(), 'aiCredits');
    const license = deps.getLicense();
    const quota = ensureAiCredits(amount, reason);
    const prevUsage = (license?.usage ?? {}) as Record<string, unknown>;
    const used = (toNumber(prevUsage.aiCreditsUsed) ?? 0) + amount;
    const remaining = computeRemaining(quota.limit, used);
    deps.patchLicense?.({
      usage: {
        ...prevUsage,
        aiCreditsUsed: used,
        ...(remaining !== null ? { aiCreditsRemaining: remaining } : {}),
      },
    } as Partial<AsukaLicenseEntitlements>);
    emitAfter('ai.credits.consumed', { amount, reason, used, remaining });
    return getCommercialQuotaState(deps.getLicense(), 'aiCredits');
  };

  const api = {
    getAvailability: () => ({
      textToImage: deps.canUseFeature('aiCredits'),
      backgroundRemoval: deps.canUseFeature('aiCredits'),
    }),
    getQuota: () => getCommercialQuotaState(deps.getLicense(), 'aiCredits'),
    consumeCredits: (amount = 1, reason = 'Manual AI usage') => consumeAiCredits(amount, reason),
    textToImage: {
      generate: async (input: AsukaAiTextToImageInput) => {
        const prompt = String(input?.prompt ?? '').trim();
        if (!prompt) throw new Error('[AsukaDesigner] textToImage.generate requires a prompt.');
        const config = options.textToImage ?? {};
        const payload: AsukaAiTextToImageInput = {
          ...input,
          prompt,
          aspectRatio: input?.aspectRatio ?? config.defaultAspectRatio,
          model: input?.model ?? config.defaultModel,
          stylePreset: input?.stylePreset ?? config.defaultStylePreset,
        };
        const cost = Math.max(1, Number(config.creditCost ?? 1) || 1);
        ensureAiCredits(cost, 'Generating AI images');
        emitBefore('ai.textToImage.generate', { input: payload, credits: cost });
        const result = config.handler
          ? await config.handler(payload)
          : config.endpoint
            ? await (async () => {
                const compactToken = getCompactLicenseToken(deps.getLicenseToken?.());
                const response = await fetch(config.endpoint!, {
                  method: 'POST',
                  headers: buildAiAuthHeaders({ 'Content-Type': 'application/json', ...(config.headers ?? {}) }, compactToken),
                  credentials: config.credentials,
                  body: JSON.stringify({
                    ...payload,
                    ...(compactToken ? { token: compactToken, licenseToken: compactToken } : {}),
                  }),
                });
                const responsePayload = await readAiResponse(response);
                if (!response.ok || responsePayload?.ok === false) {
                  throw getAiResponseError(responsePayload, `AI image generation failed: ${response.status}`);
                }
                return responsePayload as AsukaAiTextToImageResult;
              })()
            : null;
        if (!result) throw new Error('[AsukaDesigner] No text-to-image handler or endpoint configured.');
        const mirroredServerUsage = applyServerUsage(deps, extractServerUsage(result));
        if (!mirroredServerUsage) consumeAiCredits(cost, 'textToImage');
        const finalResult = { ...result, creditsUsed: cost } as AsukaAiTextToImageResult;
        emitAfter('ai.textToImage.generate', { result: { ...finalResult, blob: finalResult.blob ? '[binary]' : undefined } });
        return finalResult;
      },
    },
    backgroundRemoval: {
      remove: async (input: AsukaBackgroundRemovalInput) => {
        const config = options.backgroundRemoval ?? {};
        if (!input?.url && !input?.file) throw new Error('[AsukaDesigner] backgroundRemoval.remove requires a url or file.');
        const cost = Math.max(1, Number(config.creditCost ?? 1) || 1);
        ensureAiCredits(cost, 'Removing image backgrounds');
        emitBefore('ai.backgroundRemoval.remove', { input: { ...input, file: input.file ? '[binary]' : undefined }, credits: cost });
        const result = config.handler
          ? await config.handler(input)
          : config.endpoint
            ? await (async () => {
                const hasFile = !!input.file;
                const compactToken = getCompactLicenseToken(deps.getLicenseToken?.());
                const response = await fetch(config.endpoint!, {
                  method: 'POST',
                  headers: hasFile
                    ? buildAiAuthHeaders(config.headers ?? {}, compactToken)
                    : buildAiAuthHeaders({ 'Content-Type': 'application/json', ...(config.headers ?? {}) }, compactToken),
                  credentials: config.credentials,
                  body: hasFile
                    ? (() => {
                        const body = new FormData();
                        body.append('file', input.file as Blob);
                        if (input.objectId) body.append('objectId', input.objectId);
                        if (input.metadata) body.append('metadata', JSON.stringify(input.metadata));
                        if (compactToken) {
                          body.append('token', compactToken);
                          body.append('licenseToken', compactToken);
                        }
                        return body;
                      })()
                    : JSON.stringify({
                        url: input.url,
                        objectId: input.objectId,
                        metadata: input.metadata,
                        ...(compactToken ? { token: compactToken, licenseToken: compactToken } : {}),
                      }),
                });
                const responsePayload = await readAiResponse(response);
                if (!response.ok || responsePayload?.ok === false) {
                  throw getAiResponseError(responsePayload, `AI background removal failed: ${response.status}`);
                }
                return responsePayload as AsukaBackgroundRemovalResult;
              })()
            : deps.fallbackBackgroundRemoval
              ? await deps.fallbackBackgroundRemoval(input)
              : null;
        if (!result) throw new Error('[AsukaDesigner] No background-removal handler or endpoint configured.');
        const mirroredServerUsage = applyServerUsage(deps, extractServerUsage(result));
        if (!mirroredServerUsage) consumeAiCredits(cost, 'backgroundRemoval');
        emitAfter('ai.backgroundRemoval.remove', { result: { ...result, blob: result.blob ? '[binary]' : undefined } });
        return { ...result } as AsukaBackgroundRemovalResult;
      },
    },
  };

  return api;
}

export type AsukaAiApi = ReturnType<typeof createAsukaAiRuntime>;
