import type { ProviderAdapter, ProviderCustomModel } from "#cli/compat/providers";
import type { DimcodeUserCacheV1, DimcodeUserConfigV1 } from "../cli/user-cache";
import { getBuiltinProvider, getProviderDisplayName } from "#cli/compat/providers";
import {
  loadDimcodeUserCache,
  loadDimcodeUserConfig,
  saveDimcodeUserCache,
  saveDimcodeUserConfig,
} from "../cli/user-cache";

type RuntimeGlobals = typeof globalThis & {
  process?: NodeJS.Process;
};

type MutableStoreState = {
  context?: {
    cwd?: string;
  };
  config?: Record<string, unknown>;
  provider?: Record<string, unknown> & {
    activeId?: string;
    activeModelByProvider?: ActiveModelRecord;
    connections?: ProviderConnectionsRecord;
  };
  ui?: Record<string, unknown> & {
    workspaceSelectionByCwd?: WorkspaceSelectionRecord;
  };
  sessionId?: string;
};

type GoatChainStore = {
  state?: {
    context?: MutableStoreState["context"];
    config?: MutableStoreState["config"];
    provider?: MutableStoreState["provider"];
    ui?: MutableStoreState["ui"];
    sessionId?: string;
  };
  setEnvVar?: (key: string, value: string | undefined) => void;
};

export interface OAuthProviderConnectionPatch {
  providerId: string;
  clear?: boolean;
  baseUrl?: string | null;
  apiKey?: string | null;
  models?: readonly ProviderCustomModel[] | null;
}

export interface OAuthActiveModelPatch {
  providerId: string;
  clear?: boolean;
  modelId?: string | null;
}

export interface OAuthWorkspaceSelectionPatch {
  cwd?: string | null;
  clear?: boolean;
  providerId?: string | null;
  modelId?: string | null;
}

export interface OAuthRuntimePatch {
  env?: Record<string, string | undefined>;
  store?: GoatChainStore | null;
  providerId?: string | null;
  providerName?: string | null;
  adapter?: ProviderAdapter | null;
  modelId?: string | null;
  baseUrl?: string | null;
  apiKey?: string | null;
  authMethod?: "oauth" | "api" | null;
  providerConnection?: OAuthProviderConnectionPatch | null;
  activeModel?: OAuthActiveModelPatch | null;
  clearProviderId?: boolean;
  clearModel?: boolean;
  clearBaseUrl?: boolean;
  clearApiKey?: boolean;
  workspaceSelection?: OAuthWorkspaceSelectionPatch | null;
  clearWorkspaceSelectionsByProvider?: readonly string[] | string;
  invalidateSession?: boolean;
}

type OAuthRuntimeTarget = Readonly<{
  getStore: () => GoatChainStore | null | undefined;
  updatePersistenceSnapshots?: (
    next: Readonly<{
      cache?: DimcodeUserCacheV1;
      config?: DimcodeUserConfigV1;
    }>,
  ) => void;
  invalidateSession?: (sessionId: string) => void;
}>;

type ScalarAction<T> =
  | Readonly<{ kind: "keep" }>
  | Readonly<{ kind: "clear" }>
  | Readonly<{ kind: "set"; value: T }>;

type WorkspaceSelectionRecord = Record<string, { providerId: string; modelId?: string }>;
type ProviderConnectionsRecord = Record<
  string,
  {
    baseUrl?: string;
    apiKey?: string;
    models?: ProviderCustomModel[];
  }
>;
type ActiveModelRecord = Record<string, string>;
type MutableSettings = Record<string, unknown>;

interface NormalizedProviderUpdate {
  kind: "keep" | "clear" | "set";
  id?: string;
  name?: string;
  adapter?: ProviderAdapter;
}

interface NormalizedProviderConnectionPatch {
  providerId: string;
  clear: boolean;
  baseUrl: ScalarAction<string>;
  apiKey: ScalarAction<string>;
  models: ScalarAction<ProviderCustomModel[]>;
}

interface NormalizedActiveModelPatch {
  providerId: string;
  model: ScalarAction<string>;
}

interface NormalizedWorkspaceSelectionPatch {
  cwd: string;
  kind: "clear" | "set";
  providerId?: string;
  modelId?: string;
}

interface NormalizedOAuthRuntimePatch {
  env: Record<string, string | undefined>;
  store: GoatChainStore | null;
  provider: NormalizedProviderUpdate;
  model: ScalarAction<string>;
  baseUrl: ScalarAction<string>;
  apiKey: ScalarAction<string>;
  authMethod: ScalarAction<"oauth" | "api">;
  providerConnection?: NormalizedProviderConnectionPatch;
  activeModel?: NormalizedActiveModelPatch;
  workspaceSelection?: NormalizedWorkspaceSelectionPatch;
  clearWorkspaceSelectionsByProvider: readonly string[];
  invalidateSession: boolean;
}

let runtimeTarget: OAuthRuntimeTarget | null = null;

const KEEP: ScalarAction<never> = { kind: "keep" };

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals;
}

function runtimeEnv(): Record<string, string | undefined> {
  return runtimeGlobals().process?.env ?? {};
}

function normalizeText(value: unknown): string {
  return String(value ?? "").trim();
}

function normalizeNonEmpty(value: unknown): string | undefined {
  const next = normalizeText(value);
  return next || undefined;
}

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

function cloneModels(
  models: readonly ProviderCustomModel[] | null | undefined,
): ProviderCustomModel[] | undefined {
  if (!Array.isArray(models)) return undefined;

  return models
    .map((model) => {
      const id = normalizeNonEmpty(model?.id);
      if (!id) return null;

      const name = normalizeNonEmpty(model?.name);
      return {
        id,
        ...(name ? { name } : {}),
        ...(model?.limit ? { limit: JSON.parse(JSON.stringify(model.limit)) } : {}),
        ...(typeof model?.vision === "boolean" ? { vision: model.vision } : {}),
        ...(model?.reasoning ? { reasoning: JSON.parse(JSON.stringify(model.reasoning)) } : {}),
      } satisfies ProviderCustomModel;
    })
    .filter((model): model is ProviderCustomModel => Boolean(model));
}

function createTextAction(value: string | null | undefined, clear = false): ScalarAction<string> {
  if (clear) return { kind: "clear" };
  if (value === undefined) return KEEP;
  const next = normalizeNonEmpty(value);
  return next ? { kind: "set", value: next } : { kind: "clear" };
}

function createLiteralAction<T extends string>(value: T | null | undefined): ScalarAction<T> {
  if (value === undefined) return KEEP;
  if (value === null) return { kind: "clear" };
  return { kind: "set", value };
}

function createModelsAction(
  models: readonly ProviderCustomModel[] | null | undefined,
): ScalarAction<ProviderCustomModel[]> {
  if (models === undefined) return KEEP;
  if (models === null) return { kind: "clear" };
  return { kind: "set", value: cloneModels(models) ?? [] };
}

function resolveProviderName(providerId: string, explicit?: string | null): string {
  const next = normalizeNonEmpty(explicit);
  if (next) return next;
  return getProviderDisplayName(getBuiltinProvider(providerId) ?? { id: providerId });
}

function resolveWorkspaceCwd(
  store: GoatChainStore | null | undefined,
  patch: OAuthWorkspaceSelectionPatch | null | undefined,
): string {
  return normalizeNonEmpty(patch?.cwd) ?? normalizeNonEmpty(store?.state?.context?.cwd) ?? ".";
}

function normalizeProviderUpdate(patch: Readonly<OAuthRuntimePatch>): NormalizedProviderUpdate {
  if (patch.clearProviderId) return { kind: "clear" };

  const providerId = normalizeNonEmpty(patch.providerId);
  if (!providerId) return { kind: "keep" };

  return {
    kind: "set",
    id: providerId,
    name: resolveProviderName(providerId, patch.providerName),
    adapter: normalizeAdapter(patch.adapter) ?? "openai",
  };
}

function normalizeProviderConnectionPatch(
  patch: OAuthProviderConnectionPatch | null | undefined,
): NormalizedProviderConnectionPatch | undefined {
  if (!patch) return undefined;

  const providerId = normalizeNonEmpty(patch.providerId);
  if (!providerId) return undefined;

  if (patch.clear) {
    return {
      providerId,
      clear: true,
      baseUrl: KEEP,
      apiKey: KEEP,
      models: KEEP,
    };
  }

  return {
    providerId,
    clear: false,
    baseUrl: createTextAction(patch.baseUrl),
    apiKey: createTextAction(patch.apiKey),
    models: createModelsAction(patch.models),
  };
}

function normalizeInferredProviderConnectionPatch(
  patch: Readonly<OAuthRuntimePatch>,
  store: GoatChainStore | null | undefined,
): NormalizedProviderConnectionPatch | undefined {
  if (
    patch.providerConnection ||
    !patch.clearProviderId ||
    !patch.clearBaseUrl ||
    !patch.clearApiKey ||
    !patch.activeModel?.clear
  ) {
    return undefined;
  }

  const providerId =
    normalizeNonEmpty(patch.activeModel.providerId) ??
    normalizeNonEmpty(patch.providerId) ??
    normalizeNonEmpty(store?.state?.provider?.activeId);
  if (!providerId) return undefined;

  return {
    providerId,
    clear: true,
    baseUrl: KEEP,
    apiKey: KEEP,
    models: KEEP,
  };
}

function normalizeActiveModelPatch(
  patch: OAuthActiveModelPatch | null | undefined,
): NormalizedActiveModelPatch | undefined {
  if (!patch) return undefined;

  const providerId = normalizeNonEmpty(patch.providerId);
  if (!providerId) return undefined;

  return {
    providerId,
    model: createTextAction(patch.modelId, patch.clear),
  };
}

function normalizeWorkspaceSelectionPatch(
  store: GoatChainStore | null | undefined,
  patch: OAuthWorkspaceSelectionPatch | null | undefined,
): NormalizedWorkspaceSelectionPatch | undefined {
  if (!patch) return undefined;

  const cwd = resolveWorkspaceCwd(store, patch);
  if (patch.clear) return { cwd, kind: "clear" };

  const providerId = normalizeNonEmpty(patch.providerId);
  if (!providerId) return undefined;

  const modelId = normalizeNonEmpty(patch.modelId);
  return {
    cwd,
    kind: "set",
    providerId,
    ...(modelId ? { modelId } : {}),
  };
}

function normalizeClearWorkspaceSelectionsByProvider(
  value: readonly string[] | string | undefined,
): string[] {
  const list = Array.isArray(value) ? value : value ? [value] : [];
  return [...new Set(list.map(normalizeNonEmpty).filter((item): item is string => Boolean(item)))];
}

function normalizeOAuthRuntimePatch(
  patch: Readonly<OAuthRuntimePatch>,
): NormalizedOAuthRuntimePatch {
  const store = patch.store ?? runtimeTarget?.getStore?.() ?? null;
  const env = patch.env ?? runtimeEnv();
  const providerConnection =
    normalizeProviderConnectionPatch(patch.providerConnection) ??
    normalizeInferredProviderConnectionPatch(patch, store);

  return {
    env,
    store,
    provider: normalizeProviderUpdate(patch),
    model: createTextAction(patch.modelId, patch.clearModel),
    baseUrl: createTextAction(patch.baseUrl, patch.clearBaseUrl),
    apiKey: createTextAction(patch.apiKey, patch.clearApiKey),
    authMethod: createLiteralAction(patch.authMethod),
    providerConnection,
    activeModel: normalizeActiveModelPatch(patch.activeModel),
    workspaceSelection: normalizeWorkspaceSelectionPatch(store, patch.workspaceSelection),
    clearWorkspaceSelectionsByProvider: normalizeClearWorkspaceSelectionsByProvider(
      patch.clearWorkspaceSelectionsByProvider,
    ),
    invalidateSession: Boolean(patch.invalidateSession),
  };
}

function applyScalarAction(
  target: Record<string, unknown>,
  key: string,
  action: ScalarAction<unknown>,
): void {
  if (action.kind === "keep") return;
  if (action.kind === "clear") {
    delete target[key];
    return;
  }
  target[key] = action.value;
}

function applyWorkspaceSelections(
  current: WorkspaceSelectionRecord | undefined,
  patch: Readonly<NormalizedOAuthRuntimePatch>,
): WorkspaceSelectionRecord | undefined {
  const next: WorkspaceSelectionRecord = { ...current };

  for (const providerId of patch.clearWorkspaceSelectionsByProvider) {
    for (const [cwd, selection] of Object.entries(next)) {
      if (normalizeText(selection?.providerId) === providerId) delete next[cwd];
    }
  }

  if (patch.workspaceSelection) {
    if (patch.workspaceSelection.kind === "clear") {
      delete next[patch.workspaceSelection.cwd];
    } else {
      next[patch.workspaceSelection.cwd] = {
        providerId: patch.workspaceSelection.providerId!,
        ...(patch.workspaceSelection.modelId ? { modelId: patch.workspaceSelection.modelId } : {}),
      };
    }
  }

  return Object.keys(next).length > 0 ? next : undefined;
}

function applyProviderConnections(
  current: ProviderConnectionsRecord | undefined,
  patch: NormalizedProviderConnectionPatch | undefined,
): ProviderConnectionsRecord | undefined {
  if (!patch) return current;

  const next: ProviderConnectionsRecord = { ...current };
  if (patch.clear) {
    delete next[patch.providerId];
    return Object.keys(next).length > 0 ? next : undefined;
  }

  const connection = { ...next[patch.providerId] };
  if (patch.baseUrl.kind === "set") connection.baseUrl = patch.baseUrl.value;
  else if (patch.baseUrl.kind === "clear") delete connection.baseUrl;

  if (patch.apiKey.kind === "set") connection.apiKey = patch.apiKey.value;
  else if (patch.apiKey.kind === "clear") delete connection.apiKey;

  if (patch.models.kind === "set") connection.models = patch.models.value;
  else if (patch.models.kind === "clear") delete connection.models;

  if (Object.keys(connection).length > 0) next[patch.providerId] = connection;
  else delete next[patch.providerId];

  return Object.keys(next).length > 0 ? next : undefined;
}

function applyActiveModels(
  current: ActiveModelRecord | undefined,
  patch: NormalizedActiveModelPatch | undefined,
): ActiveModelRecord | undefined {
  if (!patch) return current;

  const next: ActiveModelRecord = { ...current };
  if (patch.model.kind === "set") next[patch.providerId] = patch.model.value;
  else if (patch.model.kind === "clear") delete next[patch.providerId];

  return Object.keys(next).length > 0 ? next : undefined;
}

function buildEnvUpdates(
  patch: Readonly<NormalizedOAuthRuntimePatch>,
): Record<string, string | undefined> {
  const updates: Record<string, string | undefined> = {};

  if (patch.provider.kind === "set") {
    updates.DIMCODE_PROVIDER_ADAPTER = patch.provider.adapter;
    updates.DIMCODE_PROVIDER_ID = patch.provider.id;
    updates.DIMCODE_PROVIDER_NAME = patch.provider.name;
  } else if (patch.provider.kind === "clear") {
    updates.DIMCODE_PROVIDER_ADAPTER = undefined;
    updates.DIMCODE_PROVIDER_ID = undefined;
    updates.DIMCODE_PROVIDER_NAME = undefined;
  }

  if (patch.model.kind === "set") {
    updates.DIMCODE_MODEL = patch.model.value;
  } else if (patch.model.kind === "clear") {
    updates.DIMCODE_MODEL = undefined;
  }

  if (patch.baseUrl.kind === "set") {
    updates.DIMCODE_OPENAI_BASE_URL = patch.baseUrl.value;
    updates.OPENAI_BASE_URL = patch.baseUrl.value;
  } else if (patch.baseUrl.kind === "clear") {
    updates.DIMCODE_OPENAI_BASE_URL = undefined;
    updates.OPENAI_BASE_URL = undefined;
  }

  if (patch.apiKey.kind === "set") updates.OPENAI_API_KEY = patch.apiKey.value;
  else if (patch.apiKey.kind === "clear") updates.OPENAI_API_KEY = undefined;

  return updates;
}

function assignEnvValue(
  target: Record<string, string | undefined>,
  key: string,
  value: string | undefined,
): void {
  const next = normalizeNonEmpty(value);
  if (next) target[key] = next;
  else delete target[key];
}

function applyEnvUpdates(
  env: Record<string, string | undefined>,
  store: GoatChainStore | null | undefined,
  updates: Record<string, string | undefined>,
): void {
  const procEnv = runtimeGlobals().process?.env;

  for (const [key, value] of Object.entries(updates)) {
    const next = normalizeNonEmpty(value);

    if (store?.setEnvVar) store.setEnvVar(key, next);
    else if (procEnv) assignEnvValue(procEnv, key, next);

    if (env && env !== procEnv) assignEnvValue(env, key, next);
  }
}

function applyNormalizedPatchToCache(
  cache: DimcodeUserCacheV1,
  patch: Readonly<NormalizedOAuthRuntimePatch>,
): DimcodeUserCacheV1 {
  const nextSettings = { ...cache.settings } as MutableSettings;

  applyScalarAction(nextSettings, "model", patch.model);
  applyScalarAction(nextSettings, "baseUrl", patch.baseUrl);
  applyScalarAction(nextSettings, "apiKey", patch.apiKey);
  applyScalarAction(nextSettings, "authMethod", patch.authMethod);

  const workspaceSelectionByCwd = applyWorkspaceSelections(
    (nextSettings.workspaceSelectionByCwd as WorkspaceSelectionRecord | undefined) ?? undefined,
    patch,
  );
  if (workspaceSelectionByCwd) nextSettings.workspaceSelectionByCwd = workspaceSelectionByCwd;
  else delete nextSettings.workspaceSelectionByCwd;

  return {
    ...cache,
    updatedAt: Date.now(),
    settings: nextSettings,
  };
}

function applyNormalizedPatchToConfig(
  config: DimcodeUserConfigV1,
  patch: Readonly<NormalizedOAuthRuntimePatch>,
): DimcodeUserConfigV1 {
  const nextSettings = { ...config.settings } as MutableSettings;

  if (patch.provider.kind === "set") nextSettings.providerId = patch.provider.id;
  else if (patch.provider.kind === "clear") delete nextSettings.providerId;

  if (patch.providerConnection) {
    const providerConnections = applyProviderConnections(
      nextSettings.providerConnections as ProviderConnectionsRecord | undefined,
      patch.providerConnection,
    );
    if (providerConnections) nextSettings.providerConnections = providerConnections;
    else delete nextSettings.providerConnections;
  }

  if (patch.activeModel) {
    const activeModelByProvider = applyActiveModels(
      nextSettings.activeModelByProvider as ActiveModelRecord | undefined,
      patch.activeModel,
    );
    if (activeModelByProvider) nextSettings.activeModelByProvider = activeModelByProvider;
    else delete nextSettings.activeModelByProvider;
  }

  return {
    ...config,
    updatedAt: Date.now(),
    settings: nextSettings,
  };
}

function applyNormalizedPatchToStore(
  store: GoatChainStore | null | undefined,
  patch: Readonly<NormalizedOAuthRuntimePatch>,
): void {
  if (!store) return;

  const state = (store.state ??= {});
  state.config = state.config ?? {};
  state.provider = state.provider ?? {};
  state.ui = state.ui ?? {};

  const config = state.config;
  const provider = state.provider;
  const ui = state.ui;

  applyScalarAction(config, "model", patch.model);
  applyScalarAction(config, "baseUrl", patch.baseUrl);
  applyScalarAction(config, "apiKey", patch.apiKey);

  if (patch.provider.kind === "set") provider.activeId = patch.provider.id;
  else if (patch.provider.kind === "clear") delete provider.activeId;

  if (patch.activeModel || provider.activeModelByProvider) {
    provider.activeModelByProvider =
      applyActiveModels(
        provider.activeModelByProvider as ActiveModelRecord | undefined,
        patch.activeModel,
      ) ?? {};
  }

  if (patch.providerConnection || provider.connections) {
    provider.connections =
      applyProviderConnections(
        provider.connections as ProviderConnectionsRecord | undefined,
        patch.providerConnection,
      ) ?? {};
  }

  if (
    patch.workspaceSelection ||
    patch.clearWorkspaceSelectionsByProvider.length > 0 ||
    ui.workspaceSelectionByCwd
  ) {
    ui.workspaceSelectionByCwd =
      applyWorkspaceSelections(
        ui.workspaceSelectionByCwd as WorkspaceSelectionRecord | undefined,
        patch,
      ) ?? {};
  }

  applyEnvUpdates(patch.env, store, buildEnvUpdates(patch));
}

export function registerOAuthRuntimeSyncTarget(target: OAuthRuntimeTarget | null): void {
  runtimeTarget = target;
}

export function applyOAuthRuntimePatchToCache(
  cache: DimcodeUserCacheV1,
  patch: Readonly<OAuthRuntimePatch>,
  store?: GoatChainStore | null,
): DimcodeUserCacheV1 {
  const normalized = normalizeOAuthRuntimePatch({
    ...patch,
    ...(store !== undefined ? { store } : {}),
  });
  return applyNormalizedPatchToCache(cache, normalized);
}

export function applyOAuthRuntimePatchToConfig(
  config: DimcodeUserConfigV1,
  patch: Readonly<OAuthRuntimePatch>,
): DimcodeUserConfigV1 {
  return applyNormalizedPatchToConfig(config, normalizeOAuthRuntimePatch(patch));
}

export function applyOAuthRuntimePatchToStore(
  store: GoatChainStore | null | undefined,
  patch: Readonly<OAuthRuntimePatch>,
): void {
  applyNormalizedPatchToStore(
    store,
    normalizeOAuthRuntimePatch({
      ...patch,
      store,
    }),
  );
}

export async function syncOAuthRuntimeState(
  patch: Readonly<OAuthRuntimePatch>,
): Promise<Readonly<{ cache: DimcodeUserCacheV1; config: DimcodeUserConfigV1 }>> {
  const normalized = normalizeOAuthRuntimePatch(patch);
  const [{ filePath: cachePath, cache }, { filePath: configPath, config }] = await Promise.all([
    loadDimcodeUserCache(normalized.env),
    loadDimcodeUserConfig(normalized.env),
  ]);

  const nextCache = applyNormalizedPatchToCache(cache, normalized);
  const nextConfig = applyNormalizedPatchToConfig(config, normalized);

  await Promise.all([
    saveDimcodeUserCache(cachePath, nextCache),
    saveDimcodeUserConfig(configPath, nextConfig),
  ]);

  applyNormalizedPatchToStore(normalized.store, normalized);
  runtimeTarget?.updatePersistenceSnapshots?.({
    cache: nextCache,
    config: nextConfig,
  });

  if (normalized.invalidateSession) {
    const sessionId = normalizeNonEmpty(normalized.store?.state?.sessionId);
    if (sessionId) runtimeTarget?.invalidateSession?.(sessionId);
  }

  return { cache: nextCache, config: nextConfig };
}
