import {
  buildAuthHealthSummary,
  DEFAULT_OAUTH_WARN_MS,
  formatRemainingShort,
} from "../agents/auth-health.js";
import {
  type AuthCredentialReasonCode,
  ensureAuthProfileStore,
  repairOAuthProfileIdMismatch,
  resolveApiKeyForProfile,
  resolveProfileUnusableUntilForDisplay,
} from "../agents/auth-profiles.js";
import { formatAuthDoctorHint } from "../agents/auth-profiles/doctor.js";
import { updateAuthProfileStoreWithLock } from "../agents/auth-profiles/store.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolvePluginProviders } from "../plugins/providers.runtime.js";
import { note } from "../terminal/note.js";
import type { DoctorPrompter } from "./doctor-prompter.js";
import {
  buildProviderAuthRecoveryHint,
  resolveProviderAuthLoginCommand,
} from "./provider-auth-guidance.js";

export async function maybeRepairLegacyOAuthProfileIds(
  cfg: OpenClawConfig,
  prompter: DoctorPrompter,
): Promise<OpenClawConfig> {
  const store = ensureAuthProfileStore();
  let nextCfg = cfg;
  const providers = resolvePluginProviders({
    config: cfg,
    env: process.env,
    bundledProviderAllowlistCompat: true,
    bundledProviderVitestCompat: true,
  });
  for (const provider of providers) {
    for (const repairSpec of provider.oauthProfileIdRepairs ?? []) {
      const repair = repairOAuthProfileIdMismatch({
        cfg: nextCfg,
        store,
        provider: provider.id,
        legacyProfileId: repairSpec.legacyProfileId,
      });
      if (!repair.migrated || repair.changes.length === 0) {
        continue;
      }

      note(repair.changes.map((c) => `- ${c}`).join("\n"), "Auth profiles");
      const apply = await prompter.confirm({
        message: `Update ${repairSpec.promptLabel ?? provider.label} OAuth profile id in config now?`,
        initialValue: true,
      });
      if (!apply) {
        continue;
      }
      nextCfg = repair.config;
    }
  }
  return nextCfg;
}

function pruneAuthOrder(
  order: Record<string, string[]> | undefined,
  profileIds: Set<string>,
): { next: Record<string, string[]> | undefined; changed: boolean } {
  if (!order) {
    return { next: order, changed: false };
  }
  let changed = false;
  const next: Record<string, string[]> = {};
  for (const [provider, list] of Object.entries(order)) {
    const filtered = list.filter((id) => !profileIds.has(id));
    if (filtered.length !== list.length) {
      changed = true;
    }
    if (filtered.length > 0) {
      next[provider] = filtered;
    }
  }
  return { next: Object.keys(next).length > 0 ? next : undefined, changed };
}

function pruneAuthProfiles(
  cfg: OpenClawConfig,
  profileIds: Set<string>,
): { next: OpenClawConfig; changed: boolean } {
  const profiles = cfg.auth?.profiles;
  const order = cfg.auth?.order;
  const nextProfiles = profiles ? { ...profiles } : undefined;
  let changed = false;

  if (nextProfiles) {
    for (const id of profileIds) {
      if (id in nextProfiles) {
        delete nextProfiles[id];
        changed = true;
      }
    }
  }

  const prunedOrder = pruneAuthOrder(order, profileIds);
  if (prunedOrder.changed) {
    changed = true;
  }

  if (!changed) {
    return { next: cfg, changed: false };
  }

  const nextAuth =
    nextProfiles || prunedOrder.next
      ? {
          ...cfg.auth,
          profiles: nextProfiles && Object.keys(nextProfiles).length > 0 ? nextProfiles : undefined,
          order: prunedOrder.next,
        }
      : undefined;

  return {
    next: {
      ...cfg,
      auth: nextAuth,
    },
    changed: true,
  };
}

export async function maybeRemoveDeprecatedCliAuthProfiles(
  cfg: OpenClawConfig,
  prompter: DoctorPrompter,
): Promise<OpenClawConfig> {
  const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: false });
  const providers = resolvePluginProviders({
    config: cfg,
    env: process.env,
    bundledProviderAllowlistCompat: true,
    bundledProviderVitestCompat: true,
  });
  const deprecatedEntries = providers.flatMap((provider) =>
    (provider.deprecatedProfileIds ?? [])
      .filter((profileId) => store.profiles[profileId] || cfg.auth?.profiles?.[profileId])
      .map((profileId) => ({
        profileId,
        providerId: provider.id,
        providerLabel: provider.label,
      })),
  );
  const deprecated = new Set(deprecatedEntries.map((entry) => entry.profileId));

  if (deprecated.size === 0) {
    return cfg;
  }

  const lines = ["Deprecated external CLI auth profiles detected (no longer supported):"];
  for (const entry of deprecatedEntries) {
    const authCommand =
      resolveProviderAuthLoginCommand({
        provider: entry.providerId,
        config: cfg,
        env: process.env,
      }) ?? formatCliCommand("openclaw configure");
    lines.push(`- ${entry.profileId} (${entry.providerLabel}): use ${authCommand}`);
  }
  note(lines.join("\n"), "Auth profiles");

  const shouldRemove = await prompter.confirmAutoFix({
    message: "Remove deprecated CLI auth profiles now?",
    initialValue: true,
  });
  if (!shouldRemove) {
    return cfg;
  }

  await updateAuthProfileStoreWithLock({
    updater: (nextStore) => {
      let mutated = false;
      for (const id of deprecated) {
        if (nextStore.profiles[id]) {
          delete nextStore.profiles[id];
          mutated = true;
        }
        if (nextStore.usageStats?.[id]) {
          delete nextStore.usageStats[id];
          mutated = true;
        }
      }
      if (nextStore.order) {
        for (const [provider, list] of Object.entries(nextStore.order)) {
          const filtered = list.filter((id) => !deprecated.has(id));
          if (filtered.length !== list.length) {
            mutated = true;
            if (filtered.length > 0) {
              nextStore.order[provider] = filtered;
            } else {
              delete nextStore.order[provider];
            }
          }
        }
      }
      if (nextStore.lastGood) {
        for (const [provider, profileId] of Object.entries(nextStore.lastGood)) {
          if (deprecated.has(profileId)) {
            delete nextStore.lastGood[provider];
            mutated = true;
          }
        }
      }
      return mutated;
    },
  });

  const pruned = pruneAuthProfiles(cfg, deprecated);
  if (pruned.changed) {
    note(
      Array.from(deprecated.values())
        .map((id) => `- removed ${id} from config`)
        .join("\n"),
      "Doctor changes",
    );
  }
  return pruned.next;
}

type AuthIssue = {
  profileId: string;
  provider: string;
  status: string;
  reasonCode?: AuthCredentialReasonCode;
  remainingMs?: number;
};

export function resolveUnusableProfileHint(params: {
  kind: "cooldown" | "disabled";
  reason?: string;
}): string {
  if (params.kind === "disabled") {
    if (params.reason === "billing") {
      return "Top up credits (provider billing) or switch provider.";
    }
    if (params.reason === "auth_permanent" || params.reason === "auth") {
      return "Refresh or replace credentials, then retry.";
    }
  }
  return "Wait for cooldown or switch provider.";
}

export async function resolveAuthIssueHint(
  issue: AuthIssue,
  cfg: OpenClawConfig,
  store: ReturnType<typeof ensureAuthProfileStore>,
): Promise<string | null> {
  if (issue.reasonCode === "invalid_expires") {
    return "Invalid token expires metadata. Set a future Unix ms timestamp or remove expires.";
  }
  const providerHint = await formatAuthDoctorHint({
    cfg,
    store,
    provider: issue.provider,
    profileId: issue.profileId,
  });
  if (providerHint.trim()) {
    return providerHint;
  }
  return buildProviderAuthRecoveryHint({
    provider: issue.provider,
  }).replace(/^Run /, "Re-auth via ");
}

async function formatAuthIssueLine(
  issue: AuthIssue,
  cfg: OpenClawConfig,
  store: ReturnType<typeof ensureAuthProfileStore>,
): Promise<string> {
  const remaining =
    issue.remainingMs !== undefined ? ` (${formatRemainingShort(issue.remainingMs)})` : "";
  const hint = await resolveAuthIssueHint(issue, cfg, store);
  const reason = issue.reasonCode ? ` [${issue.reasonCode}]` : "";
  return `- ${issue.profileId}: ${issue.status}${reason}${remaining}${hint ? ` — ${hint}` : ""}`;
}

export async function noteAuthProfileHealth(params: {
  cfg: OpenClawConfig;
  prompter: DoctorPrompter;
  allowKeychainPrompt: boolean;
}): Promise<void> {
  const store = ensureAuthProfileStore(undefined, {
    allowKeychainPrompt: params.allowKeychainPrompt,
  });
  const unusable = (() => {
    const now = Date.now();
    const out: string[] = [];
    for (const profileId of Object.keys(store.usageStats ?? {})) {
      const until = resolveProfileUnusableUntilForDisplay(store, profileId);
      if (!until || now >= until) {
        continue;
      }
      const stats = store.usageStats?.[profileId];
      const remaining = formatRemainingShort(until - now);
      const disabledActive = typeof stats?.disabledUntil === "number" && now < stats.disabledUntil;
      const kind = disabledActive
        ? `disabled${stats.disabledReason ? `:${stats.disabledReason}` : ""}`
        : "cooldown";
      const hint = resolveUnusableProfileHint({
        kind: disabledActive ? "disabled" : "cooldown",
        reason: stats?.disabledReason,
      });
      out.push(`- ${profileId}: ${kind} (${remaining})${hint ? ` — ${hint}` : ""}`);
    }
    return out;
  })();

  if (unusable.length > 0) {
    note(unusable.join("\n"), "Auth profile cooldowns");
  }

  let summary = buildAuthHealthSummary({
    store,
    cfg: params.cfg,
    warnAfterMs: DEFAULT_OAUTH_WARN_MS,
  });

  const findIssues = () =>
    summary.profiles.filter(
      (profile) =>
        (profile.type === "oauth" || profile.type === "token") &&
        (profile.status === "expired" ||
          profile.status === "expiring" ||
          profile.status === "missing"),
    );

  let issues = findIssues();
  if (issues.length === 0) {
    return;
  }

  const shouldRefresh = await params.prompter.confirmAutoFix({
    message: "Refresh expiring OAuth tokens now? (static tokens need re-auth)",
    initialValue: true,
  });

  if (shouldRefresh) {
    const refreshTargets = issues.filter(
      (issue) =>
        issue.type === "oauth" && ["expired", "expiring", "missing"].includes(issue.status),
    );
    const errors: string[] = [];
    for (const profile of refreshTargets) {
      try {
        await resolveApiKeyForProfile({
          cfg: params.cfg,
          store,
          profileId: profile.profileId,
        });
      } catch (err) {
        errors.push(`- ${profile.profileId}: ${err instanceof Error ? err.message : String(err)}`);
      }
    }
    if (errors.length > 0) {
      note(errors.join("\n"), "OAuth refresh errors");
    }
    summary = buildAuthHealthSummary({
      store: ensureAuthProfileStore(undefined, {
        allowKeychainPrompt: false,
      }),
      cfg: params.cfg,
      warnAfterMs: DEFAULT_OAUTH_WARN_MS,
    });
    issues = findIssues();
  }

  if (issues.length > 0) {
    const issueLines = await Promise.all(
      issues.map((issue) =>
        formatAuthIssueLine(
          {
            profileId: issue.profileId,
            provider: issue.provider,
            status: issue.status,
            reasonCode: issue.reasonCode,
            remainingMs: issue.remainingMs,
          },
          params.cfg,
          store,
        ),
      ),
    );
    note(issueLines.join("\n"), "Model auth");
  }
}
