import type { CustomProvider, ProviderAdapter } from "../providers/types";
import type { DimcodeUserConfigV1 } from "./user-store";
import { randomBytes } from "node:crypto";
import { isCustomProviderId, isLegacyCustomProviderId } from "../providers";
import { normalizeNum, normalizeStr } from "./user-store";

const CUSTOM_ID_PREFIX = "cp_";
const BAILIAN_CODING_PLAN_PROVIDER_ID = "bailian-coding-plan";
const BAILIAN_OLD_BASE_PATH = "/apps/anthropic/v1";
const BAILIAN_NEW_BASE_URL = "https://coding.dashscope.aliyuncs.com/v1";

type SavedConfigSettings = NonNullable<DimcodeUserConfigV1["settings"]>;
type MutableConfigSettings = {
  customProvider?: SavedConfigSettings["customProvider"];
  customProviders?: CustomProvider[];
  providerConnections?: Record<string, { baseUrl?: string } & Record<string, unknown>>;
  providerId?: string;
  activeModelByProvider?: Record<string, string>;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return value != null && typeof value === "object" && !Array.isArray(value);
}

function normalizeLimit(input: unknown): { context?: number; output?: number } | undefined {
  const obj = isRecord(input) ? input : {};
  const context = normalizeNum(obj.context);
  const output = normalizeNum(obj.output);
  if (context == null && output == null) return undefined;
  return {
    ...(context != null ? { context } : {}),
    ...(output != null ? { output } : {}),
  };
}

function ensureCustomProviderId(id: string | undefined): string {
  const raw = normalizeStr(id);
  if (raw && isCustomProviderId(raw)) return raw;
  return `${CUSTOM_ID_PREFIX}${randomBytes(8).toString("hex")}`;
}

function normalizeAdapter(raw: unknown): ProviderAdapter | undefined {
  const value = normalizeStr(raw);
  if (
    value === "openai" ||
    value === "openai-responses" ||
    value === "gemini" ||
    value === "anthropic"
  ) {
    return value;
  }
  return undefined;
}

function normalizeModels(models: unknown): Array<{
  id: string;
  name?: string;
  limit?: { context?: number; output?: number };
}> {
  if (!Array.isArray(models)) return [];
  const entries: Array<{
    id: string;
    name?: string;
    limit?: { context?: number; output?: number };
  }> = [];
  for (const m of models) {
    if (!isRecord(m)) continue;
    const id = normalizeStr(m.id);
    if (!id) continue;
    const name = normalizeStr(m.name);
    const limit = normalizeLimit(m.limit);
    entries.push({ id, ...(name ? { name } : {}), ...(limit ? { limit } : {}) });
  }
  return entries;
}

function normalizeCustomProviders(input: unknown): CustomProvider[] {
  if (!Array.isArray(input)) return [];
  const normalized: CustomProvider[] = [];
  for (const item of input) {
    if (!isRecord(item)) continue;
    const id = normalizeStr(item.id);
    const name = normalizeStr(item.name);
    const adapter = normalizeAdapter(item.adapter);
    if (!id || !name || !adapter) continue;
    normalized.push({
      id: ensureCustomProviderId(id) as CustomProvider["id"],
      name,
      adapter,
      baseUrl: normalizeStr(item.baseUrl),
      apiKey: normalizeStr(item.apiKey),
      models: normalizeModels(item.models),
    });
  }
  return normalized;
}

function hasLegacyCustomProvider(settings: MutableConfigSettings): boolean {
  const legacy = settings?.customProvider ?? {};
  const baseUrl = normalizeStr(legacy.baseUrl);
  const apiKey = normalizeStr(legacy.apiKey);
  const models = normalizeModels(legacy.models);
  return Boolean(baseUrl || apiKey || models.length);
}

function buildLegacyCustomProvider(settings: MutableConfigSettings): CustomProvider {
  const legacy = settings?.customProvider ?? {};
  return {
    id: ensureCustomProviderId(undefined) as CustomProvider["id"],
    name: "Custom",
    adapter: "openai",
    baseUrl: normalizeStr(legacy.baseUrl),
    apiKey: normalizeStr(legacy.apiKey),
    models: normalizeModels(legacy.models),
  };
}

function isLegacyBailianBaseUrl(raw: unknown): boolean {
  const value = normalizeStr(raw);
  if (!value) return false;
  try {
    const parsed = new URL(value);
    const normalizedPath = parsed.pathname.replace(/\/+$/, "");
    return (
      parsed.protocol === "https:" &&
      parsed.hostname === "coding.dashscope.aliyuncs.com" &&
      normalizedPath === BAILIAN_OLD_BASE_PATH
    );
  } catch {
    return false;
  }
}

function migrateBailianCodingPlanBaseUrl(settings: MutableConfigSettings): boolean {
  const providerConnections = settings?.providerConnections;
  if (!providerConnections) return false;
  const bailian = providerConnections[BAILIAN_CODING_PLAN_PROVIDER_ID];
  if (!bailian) return false;
  if (!isLegacyBailianBaseUrl(bailian.baseUrl)) return false;
  bailian.baseUrl = BAILIAN_NEW_BASE_URL;
  return true;
}

export function migrateLegacyCustomProvider(config: DimcodeUserConfigV1): Readonly<{
  customProviders: CustomProvider[];
  migrated: boolean;
}> {
  const settings = (config.settings ?? {}) as MutableConfigSettings;
  let migrated = false;
  let customProviders = normalizeCustomProviders(settings.customProviders);

  if (migrateBailianCodingPlanBaseUrl(settings)) migrated = true;

  if (!customProviders.length && hasLegacyCustomProvider(settings)) {
    customProviders = [buildLegacyCustomProvider(settings)];
    migrated = true;
  }

  if (customProviders.length) {
    settings.customProviders = customProviders;
    const activeByProvider = { ...settings.activeModelByProvider };
    if (isLegacyCustomProviderId(settings.providerId)) {
      settings.providerId = customProviders[0].id;
      migrated = true;
    }
    if (activeByProvider.custom) {
      if (!activeByProvider[customProviders[0].id])
        activeByProvider[customProviders[0].id] = activeByProvider.custom;
      delete activeByProvider.custom;
      settings.activeModelByProvider = activeByProvider;
      migrated = true;
    }
  }

  if (!config.settings && Object.keys(settings).length);
  (config as { settings?: MutableConfigSettings }).settings = settings;

  return { customProviders, migrated };
}
