import { getConfig, putConfig, getModels } from '@/lib/api';

export interface ModelInfo {
  label: string;
  value: string;
  provider: string;
  description?: string;
  baseUrl?: string;
  apiKey?: string;
}

export const MODEL_CONFIG_UPDATED_EVENT = 'zeroclaw:model-config-updated';

export type ModelConfigUpdatedDetail = {
  action: 'add' | 'update' | 'delete';
  original?: { provider: string; model: string };
  current?: { provider: string; model: string };
};

const LOCAL_MODELS_KEY = 'zeroclaw_configured_models_v1';

type PersistedModel = {
  label: string;
  value: string;
  provider: string;
  description?: string;
  baseUrl?: string;
  apiKey?: string;
};

function normalizeCustomPersistedModel(item: PersistedModel): PersistedModel {
  const provider = item.provider?.trim();
  const baseUrl = item.baseUrl?.trim();
  if (provider === 'custom' && baseUrl) {
    return {
      ...item,
      provider: `custom:${baseUrl}`,
      baseUrl,
    };
  }
  return item;
}

// Keep this list in sync with the Agent chat composer selector.
const TRIAL_MODEL_LABELS: Record<string, string> = {
  'qwen-plus': '通义千问 Plus',
  'qwq-32b': '通义千问 QWQ-32B',
  'deepseek-r1': 'DeepSeek R1',
  'deepseek-v3': 'DeepSeek V3',
};

const TRIAL_MODEL_DESCRIPTIONS: Record<string, string> = {
  'qwen-plus': '通用对话，平衡性能与成本',
  'qwq-32b': '思考模型，强推理能力',
  'deepseek-r1': '深度推理模型',
  'deepseek-v3': '新一代深度思考模型',
};

export async function loadTrialModels(): Promise<ModelInfo[]> {
  try {
    const response = await getModels();
    return response.models
      .filter((m) => m.source === 'trial')
      .map((m) => ({
        label: TRIAL_MODEL_LABELS[m.model] ?? m.model,
        value: m.model,
        provider: 'kylin-proxy',
        description: TRIAL_MODEL_DESCRIPTIONS[m.model] ?? '试用模型',
      }));
  } catch {
    return [];
  }
}

function safeReadLocalModels(): PersistedModel[] {
  try {
    const raw = localStorage.getItem(LOCAL_MODELS_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw) as PersistedModel[];
    if (!Array.isArray(parsed)) return [];
    return parsed
      .filter((item) => item && item.value && item.provider)
      .map((item) => normalizeCustomPersistedModel(item));
  } catch {
    return [];
  }
}

function safeWriteLocalModels(models: PersistedModel[]) {
  localStorage.setItem(LOCAL_MODELS_KEY, JSON.stringify(models));
}

function emitModelConfigUpdated(detail: ModelConfigUpdatedDetail) {
  if (typeof window === 'undefined') {
    return;
  }
  window.dispatchEvent(new CustomEvent<ModelConfigUpdatedDetail>(MODEL_CONFIG_UPDATED_EVENT, { detail }));
}

function dedupeModels(models: ModelInfo[]): ModelInfo[] {
  const seen = new Set<string>();
  return models.filter((model) => {
    const key = `${model.provider}::${model.value}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function upsertTomlStringKey(rawToml: string, key: string, value: string): string {
  const escapedValue = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  const replacementLine = `${key} = "${escapedValue}"`;
  const lines = rawToml.split('\n');

  // Only edit top-level keys before the first table header.
  let firstTableIndex = lines.findIndex((line) => /^\s*\[[^\]]+\]\s*$/.test(line));
  if (firstTableIndex < 0) {
    firstTableIndex = lines.length;
  }

  const topLevelKeyRegex = new RegExp(`^\\s*${key}\\s*=.*$`);
  for (let index = 0; index < firstTableIndex; index += 1) {
    const currentLine = lines[index];
    if (currentLine && topLevelKeyRegex.test(currentLine)) {
      lines[index] = replacementLine;
      return lines.join('\n');
    }
  }

  const firstLine = lines[0];
  if (firstLine !== undefined && firstLine.trim().length === 0) {
    return `${replacementLine}\n${rawToml}`;
  }
  return `${replacementLine}\n${rawToml}`;
}

function quoteTomlString(value: string): string {
  const escapedValue = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  return `"${escapedValue}"`;
}

function removeModelProviderSection(rawToml: string, provider: string): string {
  const quotedProvider = quoteTomlString(provider);
  const sectionRegex = new RegExp(
    `^\\s*\\[model_providers\\.${quotedProvider.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]\\s*$[\\s\\S]*?(?=^\\s*\\[[^\\]]+\\]\\s*$|$)`,
    'm',
  );
  if (!sectionRegex.test(rawToml)) {
    return rawToml;
  }
  return rawToml
    .replace(sectionRegex, '')
    .replace(/\n{3,}/g, '\n\n')
    .trimEnd();
}

function isQwenFamilyProvider(provider: string): boolean {
  return ['qwen', 'qwen-intl', 'qwen-us', 'dashscope', 'dashscope-intl', 'dashscope-us'].includes(provider);
}

function isCustomProvider(provider: string): boolean {
  return provider === 'custom' || provider.startsWith('custom:');
}

function resolveProviderWithBaseUrl(provider: string, baseUrl?: string): string {
  const normalizedProvider = provider.trim();
  const normalizedBaseUrl = baseUrl?.trim();
  if (normalizedProvider === 'custom') {
    if (!normalizedBaseUrl) {
      throw new Error('custom provider 需要填写 Base URL，例如 https://api.openai.com/v1');
    }
    return `custom:${normalizedBaseUrl}`;
  }
  return normalizedProvider;
}

function sanitizeHintToken(value: string): string {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 48);
}

function appendModelRoute(rawToml: string, input: { provider: string; model: string; apiKey: string }): string {
  // `model_routes = []` cannot coexist with `[[model_routes]]` tables in TOML.
  // Remove the inline empty array form before appending route blocks.
  const withoutInlineModelRoutes = rawToml.replace(/^\s*model_routes\s*=\s*\[\s*\]\s*$/m, '').trimEnd();

  const routeRegex = /\[\[model_routes\]\][\s\S]*?(?=\n\[\[model_routes\]\]|\n\[[^\]]+\]|$)/g;
  const retainedBlocks: string[] = [];
  for (const block of withoutInlineModelRoutes.match(routeRegex) ?? []) {
    const providerMatch = block.match(/^\s*provider\s*=\s*"([^"]+)"\s*$/m);
    const modelMatch = block.match(/^\s*model\s*=\s*"([^"]+)"\s*$/m);
    const sameProvider = providerMatch?.[1]?.trim().toLowerCase() === input.provider.trim().toLowerCase();
    const sameModel = modelMatch?.[1]?.trim().toLowerCase() === input.model.trim().toLowerCase();
    if (!(sameProvider && sameModel)) {
      retainedBlocks.push(block.trim());
    }
  }

  const withoutDuplicateRoute = withoutInlineModelRoutes.replace(routeRegex, '').trimEnd();
  const hint = `web-${sanitizeHintToken(input.provider)}-${sanitizeHintToken(input.model) || 'model'}`;
  const routeBlock = [
    '[[model_routes]]',
    `hint = ${quoteTomlString(hint)}`,
    `provider = ${quoteTomlString(input.provider)}`,
    `model = ${quoteTomlString(input.model)}`,
    `api_key = ${quoteTomlString(input.apiKey)}`,
    '',
  ].join('\n');

  const mergedRouteBlocks = [...retainedBlocks, routeBlock.trim()].join('\n\n');
  if (!mergedRouteBlocks.trim()) {
    return withoutDuplicateRoute;
  }
  return `${withoutDuplicateRoute}\n\n${mergedRouteBlocks}\n`;
}

function removeModelRoute(rawToml: string, input: { provider: string; model: string }): string {
  const routeRegex = /\[\[model_routes\]\][\s\S]*?(?=\n\[\[model_routes\]\]|\n\[[^\]]+\]|$)/g;
  const keptBlocks: string[] = [];
  for (const block of rawToml.match(routeRegex) ?? []) {
    const providerMatch = block.match(/^\s*provider\s*=\s*"([^"]+)"\s*$/m);
    const modelMatch = block.match(/^\s*model\s*=\s*"([^"]+)"\s*$/m);
    const sameProvider = providerMatch?.[1]?.trim().toLowerCase() === input.provider.trim().toLowerCase();
    const sameModel = modelMatch?.[1]?.trim().toLowerCase() === input.model.trim().toLowerCase();
    if (!(sameProvider && sameModel)) {
      keptBlocks.push(block.trim());
    }
  }

  const withoutRoutes = rawToml.replace(routeRegex, '').trimEnd();
  if (keptBlocks.length === 0) {
    return withoutRoutes;
  }
  return `${withoutRoutes}\n\n${keptBlocks.join('\n\n')}\n`;
}

function readRouteApiKeys(rawToml: string): Map<string, string> {
  const routeRegex = /\[\[model_routes\]\][\s\S]*?(?=\n\[\[model_routes\]\]|\n\[[^\]]+\]|$)/g;
  const routeApiKeys = new Map<string, string>();
  for (const block of rawToml.match(routeRegex) ?? []) {
    const providerMatch = block.match(/^\s*provider\s*=\s*"([^"]+)"\s*$/m);
    const modelMatch = block.match(/^\s*model\s*=\s*"([^"]+)"\s*$/m);
    const apiKeyMatch = block.match(/^\s*api_key\s*=\s*"([^"]+)"\s*$/m);
    const provider = providerMatch?.[1]?.trim();
    const model = modelMatch?.[1]?.trim();
    const apiKey = apiKeyMatch?.[1]?.trim();
    if (provider && model && apiKey) {
      routeApiKeys.set(`${provider.toLowerCase()}::${model.toLowerCase()}`, apiKey);
    }
  }
  return routeApiKeys;
}

export async function saveModelConfiguration(input: {
  provider: string;
  model: string;
  apiKey?: string;
  baseUrl?: string;
  label?: string;
}): Promise<void> {
  const provider = resolveProviderWithBaseUrl(input.provider, input.baseUrl);
  const model = input.model.trim();
  if (!provider || !model) {
    throw new Error('provider 和 model 不能为空');
  }

  const rawConfig = await getConfig();
  const apiKey = input.apiKey?.trim();
  if (isQwenFamilyProvider(provider) && !apiKey) {
    throw new Error('Qwen 需要 API Key。请填写 Key（DASHSCOPE_API_KEY）后再保存。');
  }

  let updatedConfig = upsertTomlStringKey(rawConfig, 'default_provider', provider);
  updatedConfig = upsertTomlStringKey(updatedConfig, 'default_model', model);
  if (apiKey) {
    updatedConfig = appendModelRoute(updatedConfig, {
      provider,
      model,
      apiKey,
    });
  }
  if (!isCustomProvider(provider)) {
    // Built-in providers should not be expressed via [model_providers.<id>].base_url,
    // otherwise runtime may rewrite them as custom:<base_url>.
    updatedConfig = removeModelProviderSection(updatedConfig, provider);
  }
  await putConfig(updatedConfig);

  const current = safeReadLocalModels();
  const normalized: PersistedModel = {
    label: input.label?.trim() || model,
    value: model,
    provider,
    description: '已配置模型',
    baseUrl: input.baseUrl?.trim() || undefined,
    apiKey,
  };

  const filtered = current.filter(
    (item) => !(item.provider === normalized.provider && item.value === normalized.value),
  );
  filtered.unshift(normalized);
  safeWriteLocalModels(filtered.slice(0, 100));
  emitModelConfigUpdated({
    action: 'add',
    current: { provider: normalized.provider, model: normalized.value },
  });
}

export async function updateModelConfiguration(
  original: { provider: string; model: string },
  input: {
    provider: string;
    model: string;
    apiKey?: string;
    baseUrl?: string;
    label?: string;
  },
): Promise<void> {
  const originalProvider = original.provider.trim();
  const originalModel = original.model.trim();
  const provider = resolveProviderWithBaseUrl(input.provider, input.baseUrl);
  const model = input.model.trim();
  if (!originalProvider || !originalModel || !provider || !model) {
    throw new Error('provider 和 model 不能为空');
  }

  const apiKey = input.apiKey?.trim();
  if (isQwenFamilyProvider(provider) && !apiKey) {
    throw new Error('Qwen 需要 API Key。请填写 Key（DASHSCOPE_API_KEY）后再保存。');
  }
  let updatedConfig = await getConfig();
  updatedConfig = removeModelRoute(updatedConfig, {
    provider: originalProvider,
    model: originalModel,
  });
  if (apiKey) {
    updatedConfig = appendModelRoute(updatedConfig, {
      provider,
      model,
      apiKey,
    });
  } else {
    updatedConfig = removeModelRoute(updatedConfig, {
      provider,
      model,
    });
  }
  if (!isCustomProvider(provider)) {
    updatedConfig = removeModelProviderSection(updatedConfig, provider);
  }
  await putConfig(updatedConfig);

  const current = safeReadLocalModels();
  const removedOriginal = current.filter(
    (item) => !(item.provider === originalProvider && item.value === originalModel),
  );
  const removedDuplicateTarget = removedOriginal.filter(
    (item) => !(item.provider === provider && item.value === model),
  );

  removedDuplicateTarget.unshift({
    label: input.label?.trim() || model,
    value: model,
    provider,
    description: '已配置模型',
    baseUrl: input.baseUrl?.trim() || undefined,
    apiKey,
  });
  safeWriteLocalModels(removedDuplicateTarget.slice(0, 100));
  emitModelConfigUpdated({
    action: 'update',
    original: { provider: originalProvider, model: originalModel },
    current: { provider, model },
  });
}

export async function deleteModelConfiguration(input: {
  provider: string;
  model: string;
}): Promise<void> {
  const provider = input.provider.trim();
  const model = input.model.trim();
  if (!provider || !model) {
    return;
  }
  const current = safeReadLocalModels();
  const filtered = current.filter((item) => !(item.provider === provider && item.value === model));
  safeWriteLocalModels(filtered);
  emitModelConfigUpdated({
    action: 'delete',
    original: { provider, model },
  });
}

export async function loadConfiguredModels(): Promise<ModelInfo[]> {
  const rawConfig = await getConfig().catch(() => '');
  const routeApiKeys = rawConfig ? readRouteApiKeys(rawConfig) : new Map<string, string>();

  const localModelsWithSyncedApiKeys = safeReadLocalModels().map((item) => {
    if (item.apiKey) {
      return item;
    }
    const matchedApiKey = routeApiKeys.get(`${item.provider.toLowerCase()}::${item.value.toLowerCase()}`);
    if (!matchedApiKey) {
      return item;
    }
    return {
      ...item,
      apiKey: matchedApiKey,
    };
  });
  safeWriteLocalModels(localModelsWithSyncedApiKeys.slice(0, 100));

  const localModels = localModelsWithSyncedApiKeys.map<ModelInfo>((item) => ({
    label: item.label,
    value: item.value,
    provider: item.provider,
    description: item.description ?? '已配置模型',
    baseUrl: item.baseUrl,
    apiKey: item.apiKey,
  }));

  const merged = dedupeModels(localModels);
  if (merged.length === 0) {
    return [];
  }
  return merged;
}
