import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { normalizeStaticProviderModelId } from "./model-ref-shared.js";
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
import { normalizeProviderId } from "./provider-id.js";

type LegacyRuntimeModelProviderAlias = {
  /** Legacy provider id that encoded the runtime in the model ref. */
  legacyProvider: string;
  /** Canonical provider id that should own model selection. */
  provider: string;
  /** Runtime/backend id selected for the migrated ref. */
  runtime: string;
  /** True when the runtime is a CLI backend rather than an embedded harness. */
  cli: boolean;
  /** True when doctor must write a runtime policy even if the target runtime is the default. */
  requiresRuntimePolicy: boolean;
};

const LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES = [
  {
    legacyProvider: "codex",
    provider: "openai",
    runtime: "codex",
    cli: false,
    requiresRuntimePolicy: false,
  },
  {
    legacyProvider: "codex-cli",
    provider: "openai",
    runtime: "codex",
    cli: false,
    requiresRuntimePolicy: true,
  },
  {
    legacyProvider: "claude-cli",
    provider: "anthropic",
    runtime: "claude-cli",
    cli: true,
    requiresRuntimePolicy: true,
  },
  {
    legacyProvider: "google-gemini-cli",
    provider: "google",
    runtime: "google-gemini-cli",
    cli: true,
    requiresRuntimePolicy: true,
  },
] as const satisfies readonly LegacyRuntimeModelProviderAlias[];

export function legacyRuntimeModelAliasRequiresRuntimePolicy(provider: string): boolean {
  return (
    LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.find(
      (entry) => normalizeProviderId(entry.legacyProvider) === normalizeProviderId(provider),
    )?.requiresRuntimePolicy === true
  );
}

const LEGACY_ALIAS_BY_PROVIDER = new Map(
  LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.map((entry) => [
    normalizeProviderId(entry.legacyProvider),
    entry,
  ]),
);

const CLI_RUNTIME_BY_PROVIDER = new Map(
  LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.filter((entry) => entry.cli).map((entry) => [
    `${normalizeProviderId(entry.provider)}:${normalizeProviderId(entry.runtime)}`,
    entry,
  ]),
);

const CLI_RUNTIME_ALIASES = new Set(
  LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.filter((entry) => entry.cli).map((entry) =>
    normalizeProviderId(entry.runtime),
  ),
);

const CLI_RUNTIME_PROVIDER_IDS = new Set(
  LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.filter((entry) => entry.cli).map((entry) =>
    normalizeProviderId(entry.legacyProvider),
  ),
);

const RUNTIME_COMPARISON_PROVIDER_ALIASES = new Map<string, string>([["openai-codex", "openai"]]);

export function listLegacyRuntimeModelProviderAliases(): readonly LegacyRuntimeModelProviderAlias[] {
  return LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES;
}

/** True for CLI runtime provider ids such as `claude-cli` and `google-gemini-cli`. */
export function isCliRuntimeProvider(provider: string): boolean {
  return CLI_RUNTIME_PROVIDER_IDS.has(normalizeProviderId(provider));
}

function resolveLegacyRuntimeModelProviderAlias(
  provider: string,
): LegacyRuntimeModelProviderAlias | undefined {
  return LEGACY_ALIAS_BY_PROVIDER.get(normalizeProviderId(provider));
}

export function migrateLegacyRuntimeModelRef(raw: string): {
  ref: string;
  legacyProvider: string;
  provider: string;
  model: string;
  runtime: string;
  cli: boolean;
} | null {
  const trimmed = raw.trim();
  const slash = trimmed.indexOf("/");
  if (slash <= 0 || slash >= trimmed.length - 1) {
    return null;
  }
  const alias = resolveLegacyRuntimeModelProviderAlias(trimmed.slice(0, slash));
  if (!alias) {
    return null;
  }
  const rawModel = trimmed.slice(slash + 1).trim();
  const model = normalizeStaticProviderModelId(alias.provider, rawModel);
  if (!model) {
    return null;
  }
  return {
    ref: `${alias.provider}/${model}`,
    legacyProvider: alias.legacyProvider,
    provider: alias.provider,
    model,
    runtime: alias.runtime,
    cli: alias.cli,
  };
}

/** Shared setup/default pickers hide all legacy runtime provider ids. */
export function isLegacyRuntimeModelProvider(provider: string): boolean {
  return resolveLegacyRuntimeModelProviderAlias(provider) !== undefined;
}

export function isCliRuntimeAlias(runtime: string | undefined): boolean {
  const normalized = runtime?.trim();
  return normalized ? CLI_RUNTIME_ALIASES.has(normalizeProviderId(normalized)) : false;
}

export function isCliRuntimeAliasForProvider(params: {
  runtime: string | undefined;
  provider: string | undefined;
}): boolean {
  const runtime = params.runtime?.trim();
  const provider = params.provider?.trim();
  if (!runtime || !provider) {
    return false;
  }
  return CLI_RUNTIME_BY_PROVIDER.has(
    `${normalizeProviderId(provider)}:${normalizeProviderId(runtime)}`,
  );
}

function canonicalizeRuntimeAliasProvider(provider: string): string {
  const normalized = normalizeProviderId(provider);
  return (
    RUNTIME_COMPARISON_PROVIDER_ALIASES.get(normalized) ??
    resolveLegacyRuntimeModelProviderAlias(provider)?.provider ??
    provider
  );
}

function normalizeRuntimeModelRefForComparison(raw: string): string {
  const trimmed = raw.trim();
  const slash = trimmed.indexOf("/");
  if (slash <= 0 || slash >= trimmed.length - 1) {
    return normalizeProviderId(canonicalizeRuntimeAliasProvider(trimmed));
  }
  const provider = trimmed.slice(0, slash).trim();
  const model = trimmed.slice(slash + 1).trim();
  const canonicalProvider = normalizeProviderId(canonicalizeRuntimeAliasProvider(provider));
  return model ? `${canonicalProvider}/${model}` : canonicalProvider;
}

export function areRuntimeModelRefsEquivalent(left: string, right: string): boolean {
  return (
    normalizeRuntimeModelRefForComparison(left) === normalizeRuntimeModelRefForComparison(right)
  );
}

export function shouldPreferActiveRuntimeAliasAuthLabel(params: {
  runtimeAliasModelEquivalent: boolean;
  selectedAuthLabel?: string;
  activeAuthLabel?: string;
}): boolean {
  if (!params.runtimeAliasModelEquivalent) {
    return false;
  }
  const selectedAuth = normalizeOptionalLowercaseString(params.selectedAuthLabel);
  const activeAuth = normalizeOptionalLowercaseString(params.activeAuthLabel);
  if (!activeAuth || activeAuth === "unknown") {
    return false;
  }
  return (
    selectedAuth === "unknown" ||
    (Boolean(selectedAuth?.startsWith("api-key")) &&
      (activeAuth.startsWith("oauth") || activeAuth.startsWith("token")))
  );
}

function resolveConfiguredRuntime(params: {
  cfg?: OpenClawConfig;
  provider: string;
  agentId?: string;
  modelId?: string;
}): { runtime?: string; matchedProvider?: string } {
  const policy = resolveModelRuntimePolicy({
    config: params.cfg,
    provider: params.provider,
    modelId: params.modelId,
    agentId: params.agentId,
  });
  return {
    runtime: policy.policy?.id?.trim() || undefined,
    matchedProvider: policy.matchedProvider,
  };
}

function resolveProfileRuntimeAlias(params: {
  cfg?: OpenClawConfig;
  provider: string;
  profileId: string;
}): string | undefined {
  const profile = params.cfg?.auth?.profiles?.[params.profileId];
  if (!profile?.provider) {
    return undefined;
  }
  const provider = normalizeProviderId(params.provider);
  const profileProvider = normalizeProviderId(profile.provider);
  if (!provider || !profileProvider) {
    return undefined;
  }
  const providerAuthKey = resolveProviderIdForAuth(provider, { config: params.cfg });
  const profileAuthKey = resolveProviderIdForAuth(profileProvider, { config: params.cfg });
  if (providerAuthKey !== profileAuthKey) {
    return undefined;
  }
  return CLI_RUNTIME_BY_PROVIDER.get(`${provider}:${profileProvider}`)?.runtime;
}

function resolveCliRuntimeFromAuthProfile(params: {
  cfg?: OpenClawConfig;
  provider: string;
  authProfileId?: string;
}): string | undefined {
  if (!params.cfg?.auth?.profiles) {
    return undefined;
  }
  if (params.authProfileId?.trim()) {
    return resolveProfileRuntimeAlias({
      cfg: params.cfg,
      provider: params.provider,
      profileId: params.authProfileId.trim(),
    });
  }

  const provider = normalizeProviderId(params.provider);
  const providerAuthKey = resolveProviderIdForAuth(provider, { config: params.cfg });
  const orderedProfileIds = [
    ...(params.cfg.auth.order?.[providerAuthKey] ?? []),
    ...(providerAuthKey === provider ? [] : (params.cfg.auth.order?.[provider] ?? [])),
  ];
  for (const profileId of orderedProfileIds) {
    const profile = params.cfg.auth.profiles[profileId];
    if (!profile?.provider) {
      continue;
    }
    const profileAuthKey = resolveProviderIdForAuth(profile.provider, { config: params.cfg });
    if (profileAuthKey !== providerAuthKey) {
      continue;
    }
    return resolveProfileRuntimeAlias({ cfg: params.cfg, provider, profileId });
  }

  const compatibleProfileIds = Object.entries(params.cfg.auth.profiles)
    .filter(([, profile]) => {
      if (!profile?.provider) {
        return false;
      }
      return resolveProviderIdForAuth(profile.provider, { config: params.cfg }) === providerAuthKey;
    })
    .map(([profileId]) => profileId);
  if (compatibleProfileIds.length !== 1) {
    return undefined;
  }
  const [profileId] = compatibleProfileIds;
  return profileId
    ? resolveProfileRuntimeAlias({ cfg: params.cfg, provider, profileId })
    : undefined;
}

export function resolveCliRuntimeExecutionProvider(params: {
  provider: string;
  cfg?: OpenClawConfig;
  agentId?: string;
  modelId?: string;
  authProfileId?: string;
}): string | undefined {
  const provider = normalizeProviderId(params.provider);
  const { runtime, matchedProvider } = resolveConfiguredRuntime({ ...params, provider });
  if (runtime === "pi") {
    return undefined;
  }
  if (!runtime || runtime === "auto") {
    return resolveCliRuntimeFromAuthProfile({ ...params, provider });
  }
  const effectiveProvider = provider || normalizeProviderId(matchedProvider ?? "");
  if (!effectiveProvider) {
    return undefined;
  }
  return CLI_RUNTIME_BY_PROVIDER.get(`${effectiveProvider}:${runtime}`)?.runtime;
}
