import { useCallback, type Dispatch, type SetStateAction } from "react";
import type { TFunction } from "i18next";

import type {
  ApplySettingsPayload,
  MaybeRestartHostEngine,
  PendingRestartSections,
} from "@/components/settings/contracts";
import { agentDraftFromPayload } from "@/components/settings/models/ModelsSettings";
import {
  CUSTOM_PROVIDER_CREATION_KEY,
  providerFormFromRow,
  type CustomProviderDraft,
} from "@/components/settings/models/ProviderSettings";
import type { ModelSettingsState } from "@/components/settings/models/useModelSettingsState";
import { normalizeContextWindowTokens } from "@/components/settings/shared/ModelControls";
import {
  ApiError,
  completeProviderOAuth,
  createModelConfiguration,
  createProviderSettings,
  deleteModelConfiguration,
  loginProviderOAuth,
  logoutProviderOAuth,
  migrateModelConfigurations,
  updateModelCallOrder,
  updateModelConfiguration,
  updateProviderSettings,
} from "@/lib/api";
import type { NanobotClient } from "@/lib/nanobot-client";
import type {
  ProviderOAuthAuthorizationRequired,
  ProviderOAuthCompletionResult,
  ProviderOAuthLoginResult,
  ProviderOAuthPending,
  ProviderSettingsUpdate,
  SettingsPayload,
} from "@/lib/types";

function isProviderOAuthAuthorizationRequired(
  payload: ProviderOAuthLoginResult,
): payload is ProviderOAuthAuthorizationRequired {
  return (payload as ProviderOAuthAuthorizationRequired).status === "authorization_required";
}

function isProviderOAuthPending(
  payload: ProviderOAuthCompletionResult,
): payload is ProviderOAuthPending {
  return (payload as ProviderOAuthPending).status === "pending";
}

interface ModelSettingsActionsOptions {
  state: ModelSettingsState;
  settings: SettingsPayload | null;
  client: NanobotClient;
  t: TFunction;
  applyPayload: ApplySettingsPayload;
  maybeRestartHostEngine: MaybeRestartHostEngine;
  setPendingRestartSections: Dispatch<SetStateAction<PendingRestartSections>>;
  setError: Dispatch<SetStateAction<string | null>>;
  onModelNameChange: (modelName: string | null) => void;
  remoteBrowserAccess: boolean;
  closeProviderOAuthFlow: () => void;
  installCapabilities: (names: string[]) => Promise<boolean>;
  modelDirty: boolean;
  configuredModelProviderOptions: Array<{ name: string; label: string }>;
}

export function useModelSettingsActions({
  state,
  settings,
  client,
  t,
  applyPayload,
  maybeRestartHostEngine,
  setPendingRestartSections,
  setError,
  onModelNameChange,
  remoteBrowserAccess,
  closeProviderOAuthFlow,
  installCapabilities,
  modelDirty,
  configuredModelProviderOptions,
}: ModelSettingsActionsOptions) {
  const {
    expandedProvider,
    form,
    modelCallOrder,
    modelCallOrderSaving,
    modelConfigurationSaving,
    modelMigrationSaving,
    modelPresetBeforeCreateRef,
    modelPresetCreating,
    modelPresetEditingName,
    modelPresetPendingDelete,
    providerForms,
    providerOAuthCompleting,
    providerOAuthFlowRef,
    providerOAuthResponse,
    providerSaving,
    saving,
    setEditingProviderKeys,
    setExpandedProvider,
    setForm,
    setModelCallOrder,
    setModelCallOrderSaving,
    setModelConfigurationSaving,
    setModelMigrationSaving,
    setModelPresetCreating,
    setModelPresetEditingName,
    setModelPresetNameError,
    setModelPresetPendingDelete,
    setProviderForms,
    setProviderOAuthCompleting,
    setProviderOAuthDialogError,
    setProviderOAuthFlow,
    setProviderOAuthResponse,
    setProviderSaving,
    setSaving,
    setVisibleProviderKeys,
    visibleProviderKeys,
  } = state;

  const presetNameConflict = (name: string, currentName?: string) => {
    const normalized = name.toLowerCase();
    return settings?.model_presets.some(
      (preset) =>
        !preset.is_default &&
        preset.name !== currentName &&
        preset.name.toLowerCase() === normalized,
    ) ?? false;
  };

  const showPresetNameConflict = () => {
    setModelPresetNameError(
      t("settings.models.presetNameDuplicate", {
        defaultValue: "A preset with this name already exists.",
      }),
    );
    setError(null);
  };

  const handlePresetSaveError = (reason: unknown) => {
    if (reason instanceof ApiError && reason.status === 409) {
      showPresetNameConflict();
      return;
    }
    setError((reason as Error).message);
  };

  const saveModelSettings = async () => {
    if (
      !settings ||
      saving ||
      modelCallOrderSaving ||
      modelConfigurationSaving
    ) {
      return;
    }

    if (modelPresetCreating) {
      const name = form.modelPreset.trim();
      const provider = form.provider.trim();
      const model = form.model.trim();
      if (
        !name ||
        !provider ||
        !model ||
        form.maxTokens <= 0 ||
        form.contextWindowTokens <= 0 ||
        form.temperature < 0 ||
        form.temperature > 2
      ) {
        return;
      }
      if (presetNameConflict(name)) {
        showPresetNameConflict();
        return;
      }
      setModelPresetNameError(null);
      setModelConfigurationSaving(true);
      try {
        const payload = await createModelConfiguration(client, {
          name,
          provider,
          model,
          maxTokens: form.maxTokens,
          contextWindowTokens: form.contextWindowTokens,
          temperature: form.temperature,
          reasoningEffort: form.reasoningEffort || null,
        });
        const createdPreset = payload.created_model_preset;
        const nextOrder = createdPreset ? [...modelCallOrder, createdPreset] : null;
        applyPayload(payload);
        if (createdPreset) {
          setForm(agentDraftFromPayload(payload, createdPreset));
          setModelPresetEditingName(createdPreset);
        }

        let finalPayload = payload;
        if (nextOrder) {
          const orderedPayload = await updateModelCallOrder(client, nextOrder);
          applyPayload(orderedPayload);
          finalPayload = orderedPayload;
        }
        if (createdPreset) {
          setForm(agentDraftFromPayload(finalPayload, createdPreset));
          setModelPresetEditingName(createdPreset);
        }
        modelPresetBeforeCreateRef.current = null;
        onModelNameChange(finalPayload.agent.model || null);
        setModelPresetNameError(null);
        setError(null);
      } catch (err) {
        handlePresetSaveError(err);
      } finally {
        setModelConfigurationSaving(false);
      }
      return;
    }

    if (!modelDirty) return;
    const selectedPreset = settings.model_presets.find(
      (preset) => !preset.is_default && preset.name === modelPresetEditingName,
    );
    if (!selectedPreset) return;
    const nextName = form.modelPreset.trim();
    const nameChanged = form.modelPreset !== selectedPreset.name;
    if (nameChanged && presetNameConflict(nextName, selectedPreset.name)) {
      showPresetNameConflict();
      return;
    }
    setModelPresetNameError(null);
    const reasoningEffort = form.reasoningEffort || null;
    setSaving(true);
    try {
      const payload = await updateModelConfiguration(client, {
        name: selectedPreset.name,
        newName: nameChanged ? nextName : undefined,
        model: form.model !== selectedPreset.model ? form.model : undefined,
        provider: form.provider !== selectedPreset.provider ? form.provider : undefined,
        maxTokens:
          form.maxTokens !== selectedPreset.max_tokens ? form.maxTokens : undefined,
        contextWindowTokens:
          form.contextWindowTokens !==
          normalizeContextWindowTokens(selectedPreset.context_window_tokens)
            ? form.contextWindowTokens
            : undefined,
        temperature:
          form.temperature !== selectedPreset.temperature ? form.temperature : undefined,
        reasoningEffort:
          reasoningEffort !== selectedPreset.reasoning_effort ? reasoningEffort : undefined,
      });
      applyPayload(payload);
      setForm(agentDraftFromPayload(payload, nextName));
      setModelPresetEditingName(nextName);
      onModelNameChange(payload.agent.model || null);
      setModelPresetNameError(null);
      setError(null);
    } catch (err) {
      handlePresetSaveError(err);
    } finally {
      setSaving(false);
    }
  };

  const beginModelPresetCreation = () => {
    if (!settings || saving || modelCallOrderSaving || modelConfigurationSaving) return;
    const primaryPreset = settings.model_presets.find(
      (preset) => !preset.is_default && preset.name === settings.model_call_order?.[0],
    );
    const currentProvider = primaryPreset?.provider === "auto"
      ? primaryPreset.resolved_provider ?? settings.agent.resolved_provider
      : primaryPreset?.provider ?? settings.agent.provider;
    const provider =
      configuredModelProviderOptions.find((option) => option.name === currentProvider)?.name ??
      configuredModelProviderOptions[0]?.name ??
      "";
    modelPresetBeforeCreateRef.current = modelPresetEditingName;
    setModelPresetNameError(null);
    setForm((prev) => ({
      ...prev,
      modelPreset: "",
      provider,
      model: "",
      maxTokens: primaryPreset?.max_tokens ?? settings.agent.max_tokens,
      contextWindowTokens: normalizeContextWindowTokens(
        primaryPreset?.context_window_tokens ?? settings.agent.context_window_tokens,
      ),
      temperature: primaryPreset?.temperature ?? settings.agent.temperature,
      reasoningEffort: primaryPreset?.reasoning_effort ?? settings.agent.reasoning_effort ?? "",
    }));
    setModelPresetCreating(true);
  };

  const cancelModelPresetCreation = () => {
    if (!settings || modelConfigurationSaving) return;
    const previousPreset = modelPresetBeforeCreateRef.current;
    setModelPresetCreating(false);
    setModelPresetNameError(null);
    setForm(agentDraftFromPayload(settings, previousPreset ?? undefined));
    setModelPresetEditingName(previousPreset ?? agentDraftFromPayload(settings).modelPreset);
    modelPresetBeforeCreateRef.current = null;
  };

  const changeModelCallOrder = async (nextOrder: string[]) => {
    const unchanged =
      nextOrder.length === modelCallOrder.length &&
      nextOrder.every((name, index) => name === modelCallOrder[index]);
    if (
      !settings ||
      saving ||
      modelCallOrderSaving ||
      modelConfigurationSaving ||
      nextOrder.length === 0 ||
      unchanged
    ) {
      return;
    }
    const previousOrder = [...modelCallOrder];
    setModelCallOrder(nextOrder);
    setModelCallOrderSaving(true);
    try {
      const payload = await updateModelCallOrder(client, nextOrder);
      applyPayload(payload, { preserveAgentForm: true });
      onModelNameChange(payload.agent.model || null);
      setError(null);
    } catch (err) {
      setModelCallOrder(previousOrder);
      setError((err as Error).message);
    } finally {
      setModelCallOrderSaving(false);
    }
  };

  const handleMigrateModelConfigurations = async () => {
    if (modelMigrationSaving) return;
    setModelMigrationSaving(true);
    try {
      const payload = await migrateModelConfigurations(client);
      applyPayload(payload);
      onModelNameChange(payload.agent.model || null);
      setError(null);
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setModelMigrationSaving(false);
    }
  };

  const handleDeleteModelConfiguration = async () => {
    if (
      !modelPresetPendingDelete ||
      saving ||
      modelCallOrderSaving ||
      modelConfigurationSaving
    ) {
      return;
    }
    setSaving(true);
    try {
      const payload = await deleteModelConfiguration(client, modelPresetPendingDelete.name);
      applyPayload(payload);
      setModelPresetPendingDelete(null);
      setError(null);
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setSaving(false);
    }
  };

  const saveProvider = async (providerName: string) => {
    if (providerSaving) return;
    const provider = settings?.providers.find((item) => item.name === providerName);
    if (!provider) return;
    const isOauthProvider = provider.auth_type === "oauth";
    const providerForm = providerForms[providerName] ?? providerFormFromRow(provider);
    const apiKey = providerForm.apiKey.trim();
    const apiKeyRequired = provider.api_key_required ?? true;
    if (!isOauthProvider && !provider.configured && apiKeyRequired && !apiKey) {
      setError(t("settings.byok.apiKeyRequired"));
      return;
    }
    setProviderSaving(providerName);
    try {
      const supportName = providerName === "bedrock"
        ? "bedrock"
        : providerName === "azure_openai"
          ? "azure"
          : null;
      if (supportName && !(await installCapabilities([supportName]))) return;
      const update: ProviderSettingsUpdate = { provider: providerName };
      if (!isOauthProvider) {
        update.apiKey = apiKey || undefined;
        update.apiBase = providerForm.apiBase.trim();
        if (provider.is_custom) update.displayName = providerForm.displayName.trim();
      }
      for (const field of provider.advanced_fields ?? []) {
        if (field === "api_type") update.apiType = providerForm.apiType;
        if (field === "proxy") update.proxy = providerForm.proxy.trim();
        if (field === "extra_headers") {
          update.extraHeaders = providerForm.extraHeaders.trim();
        }
        if (field === "extra_body") update.extraBody = providerForm.extraBody.trim();
        if (field === "extra_query") update.extraQuery = providerForm.extraQuery.trim();
        if (field === "thinking_style") {
          update.thinkingStyle = providerForm.thinkingStyle.trim();
        }
        if (field === "region") update.region = providerForm.region.trim();
        if (field === "profile") update.profile = providerForm.profile.trim();
      }
      const payload = await updateProviderSettings(client, update);
      applyPayload(payload);
      if (payload.requires_restart) {
        setPendingRestartSections((prev) => ({ ...prev, image: true }));
      }
      await maybeRestartHostEngine(payload);
      setProviderForms((prev) => ({
        ...prev,
        [providerName]: {
          ...providerForm,
          displayName: providerForm.displayName.trim(),
          apiKey: "",
          apiBase: providerForm.apiBase.trim(),
          proxy: providerForm.proxy.trim(),
          thinkingStyle: providerForm.thinkingStyle.trim(),
          region: providerForm.region.trim(),
          profile: providerForm.profile.trim(),
        },
      }));
      setVisibleProviderKeys((prev) => ({ ...prev, [providerName]: false }));
      setEditingProviderKeys((prev) => ({ ...prev, [providerName]: false }));
      if (!isOauthProvider) setExpandedProvider(null);
      setError(null);
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setProviderSaving(null);
    }
  };

  const createCustomProvider = async (draft: CustomProviderDraft): Promise<boolean> => {
    if (providerSaving) return false;
    setProviderSaving(CUSTOM_PROVIDER_CREATION_KEY);
    try {
      const payload = await createProviderSettings(client, {
        name: draft.name.trim(),
        apiKey: draft.apiKey.trim() || undefined,
        apiBase: draft.apiBase.trim(),
        proxy: draft.proxy.trim(),
        extraHeaders: draft.extraHeaders.trim(),
        extraBody: draft.extraBody.trim(),
        extraQuery: draft.extraQuery.trim(),
        thinkingStyle: draft.thinkingStyle.trim(),
      });
      applyPayload(payload);
      setExpandedProvider(null);
      setError(null);
      return true;
    } catch (err) {
      setError((err as Error).message);
      return false;
    } finally {
      setProviderSaving(null);
    }
  };

  const runProviderOAuth = async (providerName: string, action: "login" | "logout") => {
    if (providerSaving) return;
    let popup: Window | null = null;
    if (
      action === "login"
      && providerName === "xai_grok"
      && !remoteBrowserAccess
    ) {
      try {
        popup = window.open("about:blank", "_blank");
        if (popup) popup.opener = null;
      } catch {
        popup = null;
      }
    }
    setProviderSaving(providerName);
    try {
      const payload =
        action === "login"
          ? await loginProviderOAuth(
              client,
              providerName,
              providerName === "openai_codex" && remoteBrowserAccess,
            )
          : await logoutProviderOAuth(client, providerName);
      if (isProviderOAuthAuthorizationRequired(payload)) {
        try {
          if (popup && !popup.closed) popup.location.href = payload.authorization_url;
        } catch {
          // The dialog keeps the authorization link available when the popup was closed.
        }
        providerOAuthFlowRef.current = payload;
        setProviderOAuthFlow(payload);
        setProviderOAuthResponse("");
        setProviderOAuthDialogError(null);
        setExpandedProvider(providerName);
        setError(null);
        return;
      }
      popup?.close();
      closeProviderOAuthFlow();
      applyPayload(payload);
      setExpandedProvider(providerName);
      setError(null);
    } catch (err) {
      popup?.close();
      setError((err as Error).message);
    } finally {
      setProviderSaving(null);
    }
  };

  const completeProviderOAuthResponse = async () => {
    const flow = providerOAuthFlowRef.current;
    const authorizationResponse = providerOAuthResponse.trim();
    if (!flow || !authorizationResponse || providerOAuthCompleting) return;
    setProviderOAuthCompleting(true);
    setProviderOAuthDialogError(null);
    try {
      const payload = await completeProviderOAuth(
        client,
        flow.provider,
        flow.flow_id,
        authorizationResponse,
      );
      if (providerOAuthFlowRef.current?.flow_id !== flow.flow_id) return;
      if (isProviderOAuthPending(payload)) return;
      applyPayload(payload);
      setExpandedProvider(flow.provider);
      setError(null);
      closeProviderOAuthFlow();
    } catch (err) {
      if (providerOAuthFlowRef.current?.flow_id === flow.flow_id) {
        setProviderOAuthDialogError((err as Error).message);
      }
    } finally {
      setProviderOAuthCompleting(false);
    }
  };

  const resetProviderDraft = useCallback((providerName: string) => {
    const provider = settings?.providers.find((item) => item.name === providerName);
    if (!provider) return;
    setProviderForms((prev) => ({
      ...prev,
      [providerName]: providerFormFromRow(provider),
    }));
    setVisibleProviderKeys((prev) => ({ ...prev, [providerName]: false }));
    setEditingProviderKeys((prev) => ({ ...prev, [providerName]: false }));
  }, [settings]);

  const handleToggleProvider = useCallback((providerName: string) => {
    if (expandedProvider) resetProviderDraft(expandedProvider);
    setExpandedProvider(expandedProvider === providerName ? null : providerName);
  }, [expandedProvider, resetProviderDraft]);

  const toggleProviderKeyVisibility = (providerName: string) => {
    const isVisible = visibleProviderKeys[providerName];
    setVisibleProviderKeys((prev) => ({ ...prev, [providerName]: !isVisible }));
  };

  const toggleProviderKeyEditing = (providerName: string) => {
    setEditingProviderKeys((prev) => {
      const nextEditing = !prev[providerName];
      if (!nextEditing) {
        setProviderForms((forms) => ({
          ...forms,
          [providerName]: {
            ...(forms[providerName] ?? providerFormFromRow(
              settings?.providers.find((provider) => provider.name === providerName) ?? {
                name: providerName,
                label: providerName,
                configured: false,
              },
            )),
            apiKey: "",
          },
        }));
        setVisibleProviderKeys((visible) => ({ ...visible, [providerName]: false }));
      }
      return { ...prev, [providerName]: nextEditing };
    });
  };

  return {
    beginModelPresetCreation,
    cancelModelPresetCreation,
    changeModelCallOrder,
    completeProviderOAuthResponse,
    createCustomProvider,
    handleDeleteModelConfiguration,
    handleMigrateModelConfigurations,
    handleToggleProvider,
    resetProviderDraft,
    runProviderOAuth,
    saveModelSettings,
    saveProvider,
    toggleProviderKeyEditing,
    toggleProviderKeyVisibility,
  };
}
