import { type JSX, useState, useEffect, useCallback, useMemo, useRef } from "react";
import { z } from "zod";
import { Button } from "../ui/button";
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
import { Plus, AlertCircle, Copy, Check, Download, Upload, Scan } from "lucide-react";
import { ImportWizardDialog } from "./ImportWizardDialog";
import { ConfirmDialog } from "../ui/confirm-dialog";
import { ProviderCard } from "./ProviderCard";
import { ProviderForm } from "./ProviderForm";
import { parseJsonResponse, readApiError } from "../../lib/apiClient";
import { ProviderConfigSchema, type ProviderConfig } from "../../lib/providerContract";
import {
  createFailedProviderTestResults,
  createPendingProviderTestResults,
  ProviderTestResultsSchema,
  type ProviderTestResults as TestResults,
} from "../../lib/providerTestContract";

export type { ProviderTestResults as TestResults } from "../../lib/providerTestContract";

const ConfigPathsResponseSchema = z.object({
  providerConfig: z.string(),
});

const ImportResponseSchema = z.object({
  success: z.boolean().optional(),
  imported: z.number().optional(),
  message: z.string().optional(),
  errors: z.array(z.string()).optional(),
});

const TEST_TIMEOUT_SECONDS = 30;
const NETWORK_ERROR_MESSAGE = "Network error: could not reach the server. Is the proxy running?";

type ProviderFormData = {
  name: string;
  apiKey: string;
  models: string[];
  anthropicBaseUrl?: string;
  openaiBaseUrl?: string;
  apiDocsUrl?: string;
  source?: "company" | "personal";
};

function createProviderPayload(data: ProviderFormData) {
  return {
    name: data.name,
    apiKey: data.apiKey,
    models: data.models,
    anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : undefined,
    openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : undefined,
    apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : undefined,
    source: data.source,
  };
}

type ProvidersPanelProps = {
  externalProviders?: ProviderConfig[];
  isLoading?: boolean;
  externalTestResults?: Record<string, TestResults>;
  externalTestingProviders?: Set<string>;
  externalTestingTimeLeft?: Record<string, number>;
  onProvidersMutate?: () => Promise<unknown>;
  onTestResultsChange?: (providerId: string, results: TestResults) => void;
  onTestingProvidersChange?: (providerId: string, isTesting: boolean) => void;
  onTestingTimeLeftChange?: (providerId: string, seconds: number | undefined) => void;
};

export function ProvidersPanel({
  externalProviders,
  isLoading = false,
  externalTestResults,
  externalTestingProviders,
  externalTestingTimeLeft,
  onProvidersMutate,
  onTestResultsChange,
  onTestingProvidersChange,
  onTestingTimeLeftChange,
}: ProvidersPanelProps): JSX.Element {
  const [showForm, setShowForm] = useState(false);
  const [editingProvider, setEditingProvider] = useState<ProviderConfig | undefined>();
  const [error, setError] = useState<string | null>(null);
  const [internalTestResults, setInternalTestResults] = useState<Record<string, TestResults>>({});
  const [internalTestingProviders, setInternalTestingProviders] = useState<Set<string>>(new Set());
  const [internalTestingTimeLeft, setInternalTestingTimeLeft] = useState<Record<string, number>>(
    {},
  );
  const [configPath, setConfigPath] = useState<string | null>(null);
  const [configPathCopied, setConfigPathCopied] = useState(false);
  const [highlightedProviderId, setHighlightedProviderId] = useState<string | null>(null);
  const [showImportWizard, setShowImportWizard] = useState(false);
  const [confirmOpen, setConfirmOpen] = useState(false);
  const [deletingProviderId, setDeletingProviderId] = useState<string | null>(null);
  const [sourceFilter, setSourceFilter] = useState<"all" | "personal" | "company">("all");
  const listScrollRef = useRef<HTMLDivElement>(null);
  const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Use external state if provided (from SWR), otherwise use internal state
  const providers = externalProviders ?? [];
  const filteredProviders = useMemo(() => {
    const filtered =
      sourceFilter === "all" ? providers : providers.filter((p) => p.source === sourceFilter);
    if (sourceFilter === "all") {
      return [...filtered].sort((a, b) => {
        const order: Record<string, number> = { personal: 0, company: 1 };
        return (order[a.source ?? ""] ?? 2) - (order[b.source ?? ""] ?? 2);
      });
    }
    return filtered;
  }, [providers, sourceFilter]);
  const testResults = externalTestResults ?? internalTestResults;
  const testingProviders = externalTestingProviders ?? internalTestingProviders;
  const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
  const setTestingTimeLeft = onTestingTimeLeftChange
    ? (id: string, seconds: number | undefined) => onTestingTimeLeftChange(id, seconds)
    : (id: string, seconds: number | undefined) => {
        if (seconds === undefined) {
          setInternalTestingTimeLeft((prev) => {
            const next = { ...prev };
            delete next[id];
            return next;
          });
        } else {
          setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
        }
      };

  useEffect(() => {
    void (async () => {
      try {
        const res = await fetch("/api/config/paths");
        if (res.ok) {
          const data = await parseJsonResponse(res, ConfigPathsResponseSchema);
          setConfigPath(data.providerConfig);
        }
      } catch {
        // Ignore
      }
    })();
  }, []);

  const triggerHighlight = useCallback((providerId: string) => {
    // Cancel any existing highlight timeout
    if (highlightTimeoutRef.current !== null) {
      clearTimeout(highlightTimeoutRef.current);
    }
    setHighlightedProviderId(providerId);
    // Scroll the list to bring the card into view
    listScrollRef.current?.scrollTo({
      top: listScrollRef.current.scrollHeight,
      behavior: "smooth",
    });
    // Clear highlight after 2 seconds
    highlightTimeoutRef.current = setTimeout(() => {
      setHighlightedProviderId(null);
      highlightTimeoutRef.current = null;
    }, 2000);
  }, []);

  useEffect(
    () => () => {
      if (highlightTimeoutRef.current !== null) {
        clearTimeout(highlightTimeoutRef.current);
      }
    },
    [],
  );

  const refreshProviders = useCallback((): void => {
    onProvidersMutate?.().catch(() => {});
  }, [onProvidersMutate]);

  const updateTestResults = useCallback(
    (providerId: string, results: TestResults): void => {
      if (onTestResultsChange) {
        onTestResultsChange(providerId, results);
      } else {
        setInternalTestResults((prev) => ({ ...prev, [providerId]: results }));
      }
    },
    [onTestResultsChange],
  );

  const updateTestingState = useCallback(
    (providerId: string, isTesting: boolean): void => {
      if (onTestingProvidersChange) {
        onTestingProvidersChange(providerId, isTesting);
        return;
      }
      setInternalTestingProviders((prev) => {
        const next = new Set(prev);
        if (isTesting) next.add(providerId);
        else next.delete(providerId);
        return next;
      });
    },
    [onTestingProvidersChange],
  );

  const runTest = useCallback(
    async (providerId: string): Promise<void> => {
      // Clear previous test results when starting a new test
      updateTestResults(providerId, createPendingProviderTestResults());
      updateTestingState(providerId, true);

      // Create abort controller for this test request
      const controller = new AbortController();

      // Start countdown
      let remaining = TEST_TIMEOUT_SECONDS;
      setTestingTimeLeft(providerId, remaining);
      const intervalId = setInterval(() => {
        remaining--;
        setTestingTimeLeft(providerId, remaining);
        if (remaining <= 0) {
          clearInterval(intervalId);
          // Abort the fetch request when time runs out
          controller.abort();
        }
      }, 1000);

      try {
        const res = await fetch(`/api/providers/${providerId}/test`, {
          method: "POST",
          signal: controller.signal,
        });
        if (res.ok) {
          const results = await parseJsonResponse(res, ProviderTestResultsSchema);
          updateTestResults(providerId, results);
        } else {
          updateTestResults(
            providerId,
            createFailedProviderTestResults(
              `HTTP ${res.status}: ${res.statusText}`,
              "server_error",
            ),
          );
        }
      } catch (err) {
        // Check if this was an abort (timeout)
        const isAbort = err instanceof Error && err.name === "AbortError";
        if (isAbort) {
          updateTestResults(
            providerId,
            createFailedProviderTestResults("Request timed out", "timeout"),
          );
        }
        // If it's not an abort error, the test results won't be updated
        // which means the previous results will persist (or it will show "Not configured" reset state)
      } finally {
        // Always clear the countdown and testing state, even if an error occurs
        clearInterval(intervalId);
        setTestingTimeLeft(providerId, undefined);
        updateTestingState(providerId, false);
      }
    },
    [setTestingTimeLeft, updateTestResults, updateTestingState],
  );

  function handleAddProvider(data: ProviderFormData): void {
    void (async () => {
      try {
        const res = await fetch("/api/providers", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(createProviderPayload(data)),
        });
        if (!res.ok) {
          setError(await readApiError(res, "Failed to add provider"));
          return;
        }
        const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
        setShowForm(false);
        triggerHighlight(newProvider.id);
        refreshProviders();
        await runTest(newProvider.id);
      } catch {
        setError(NETWORK_ERROR_MESSAGE);
      }
    })();
  }

  function handleUpdateProvider(data: ProviderFormData): void {
    if (!editingProvider) return;
    void (async () => {
      try {
        const res = await fetch(`/api/providers/${editingProvider.id}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(createProviderPayload(data)),
        });
        if (!res.ok) {
          setError(await readApiError(res, "Failed to update provider"));
          return;
        }
        const updated = await parseJsonResponse(res, ProviderConfigSchema);
        setEditingProvider(undefined);
        triggerHighlight(updated.id);
        refreshProviders();
        // Only run connection test when critical fields (apiKey, model, base URLs) changed
        const criticalFieldsChanged =
          (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") ||
          JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) ||
          (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") ||
          (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "");
        if (criticalFieldsChanged) {
          await runTest(updated.id);
        }
      } catch {
        setError(NETWORK_ERROR_MESSAGE);
      }
    })();
  }

  function handleDeleteProvider(providerId: string): void {
    setDeletingProviderId(providerId);
    setConfirmOpen(true);
  }

  function executeDelete(): void {
    const providerId = deletingProviderId;
    if (providerId === null) return;
    void (async () => {
      let res: Response;
      try {
        res = await fetch(`/api/providers/${providerId}`, {
          method: "DELETE",
        });
      } catch {
        setError(NETWORK_ERROR_MESSAGE);
        return;
      }
      if (!res.ok) {
        setError(await readApiError(res, "Failed to delete provider"));
        return;
      }
      refreshProviders();
    })();
  }

  const fileInputRef = useRef<HTMLInputElement>(null);

  function handleExport(includeKeys: boolean): void {
    const url = `/api/providers/export${includeKeys ? "?includeKeys=true" : ""}`;
    void (async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) {
          setError("Failed to export providers");
          return;
        }
        const blob = await res.blob();
        const downloadUrl = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = downloadUrl;
        a.download =
          res.headers.get("Content-Disposition")?.match(/filename="(.+)"/)?.[1] ?? "providers.json";
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(downloadUrl);
      } catch {
        setError("Failed to export providers");
      }
    })();
  }

  function handleImportClick(): void {
    fileInputRef.current?.click();
  }

  function handleFileChange(e: React.ChangeEvent<HTMLInputElement>): void {
    const file = e.target.files?.[0];
    if (!file) return;

    void (async () => {
      try {
        const text = await file.text();
        const res = await fetch("/api/providers/import", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(text),
        });
        const data = await parseJsonResponse(res, ImportResponseSchema);
        if (res.ok && data.imported !== undefined && data.imported > 0) {
          refreshProviders();
          // Show success message via error state temporarily
          setError(null);
          // Use a ref or state to show success - for now just refresh list
        } else if (data.errors && data.errors.length > 0) {
          setError(data.errors.join("; "));
        } else {
          setError(data.message ?? "Import failed");
        }
      } catch {
        setError("Failed to import providers");
      }
      // Reset file input
      e.target.value = "";
    })();
  }

  // Only show loading if we are actually loading (prevents flashing when reopening Settings during test)
  if (isLoading) {
    return (
      <div className="flex items-center justify-center py-8">
        <p className="text-sm text-muted-foreground">Loading providers...</p>
      </div>
    );
  }

  if (showForm || editingProvider) {
    return (
      <div className="space-y-4">
        <div className="flex items-center justify-between sticky top-0 bg-background z-10 pb-2">
          <h3 className="text-lg font-medium">
            {editingProvider ? "Edit Provider" : "Add New Provider"}
          </h3>
        </div>
        <ProviderForm
          provider={editingProvider}
          onSubmit={editingProvider ? handleUpdateProvider : handleAddProvider}
          onCancel={() => {
            setShowForm(false);
            setEditingProvider(undefined);
          }}
        />
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between sticky top-0 z-10 bg-background pb-2">
        <h3 className="text-lg font-medium">Providers</h3>
        <div className="flex items-center gap-2">
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => handleExport(false)}
                  className="gap-1 hover:bg-muted"
                >
                  <Download className="size-3" />
                  Export
                </Button>
              </TooltipTrigger>
              <TooltipContent>Download providers as JSON for backup or sharing</TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={handleImportClick}
                  className="gap-1 hover:bg-muted"
                >
                  <Upload className="size-3" />
                  Import
                </Button>
              </TooltipTrigger>
              <TooltipContent>Import providers from an exported JSON file</TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <input
            type="file"
            ref={fileInputRef}
            accept=".json"
            onChange={handleFileChange}
            style={{ display: "none" }}
          />
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setShowImportWizard(true)}
                  className="gap-1"
                >
                  <Scan className="size-3" />
                  Scan
                </Button>
              </TooltipTrigger>
              <TooltipContent>
                Detect and import provider configs from Claude Code and OpenCode
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <Button onClick={() => setShowForm(true)} size="sm" className="gap-1">
            <Plus className="size-4" />
            Add Provider
          </Button>
        </div>
      </div>

      {configPath !== null && (
        <div className="flex items-center gap-2 text-xs text-muted-foreground bg-muted/30 rounded-md px-3 py-2">
          <span className="shrink-0">Config:</span>
          <span className="font-mono truncate" title={configPath}>
            {configPath}
          </span>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  type="button"
                  onClick={() => {
                    void window.navigator.clipboard.writeText(configPath).then(() => {
                      setConfigPathCopied(true);
                      setTimeout(() => setConfigPathCopied(false), 2000);
                    });
                  }}
                  className="shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors"
                >
                  {configPathCopied ? (
                    <Check className="size-3 text-green-500" />
                  ) : (
                    <Copy className="size-3" />
                  )}
                </button>
              </TooltipTrigger>
              <TooltipContent>Copy config file path to clipboard</TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </div>
      )}

      {error !== null && (
        <div className="flex items-center gap-2 text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2">
          <AlertCircle className="size-4 shrink-0" />
          {error}
        </div>
      )}

      {providers.length === 0 ? (
        <div className="text-center py-12 space-y-3">
          <p className="text-sm text-muted-foreground">No providers configured yet.</p>
          <Button onClick={() => setShowForm(true)} variant="outline" size="sm">
            <Plus className="size-4" />
            Add Your First Provider
          </Button>
        </div>
      ) : (
        <div className="space-y-3">
          <div className="flex gap-1 border-b border-border">
            {(["all", "personal", "company"] as const).map((tab) => (
              <TooltipProvider key={tab}>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <button
                      type="button"
                      onClick={() => setSourceFilter(tab)}
                      className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
                        sourceFilter === tab
                          ? "border-primary text-primary"
                          : "border-transparent text-muted-foreground hover:text-foreground"
                      }`}
                    >
                      {tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"}
                    </button>
                  </TooltipTrigger>
                  <TooltipContent>
                    {tab === "all"
                      ? "Show all providers"
                      : tab === "personal"
                        ? "Providers you configured yourself"
                        : "Providers set by your organization"}
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            ))}
          </div>
          <div ref={listScrollRef} className="space-y-3">
            {filteredProviders.map((provider) => (
              <ProviderCard
                key={provider.id}
                provider={provider}
                testResults={testResults[provider.id]}
                isTesting={testingProviders.has(provider.id)}
                testingTimeLeft={testingTimeLeft[provider.id]}
                isHighlighted={provider.id === highlightedProviderId}
                onEdit={(p) => setEditingProvider(p)}
                onDelete={handleDeleteProvider}
                onTest={(id: string) => {
                  void runTest(id);
                }}
              />
            ))}
          </div>
        </div>
      )}

      <ImportWizardDialog
        open={showImportWizard}
        onOpenChange={setShowImportWizard}
        onImportComplete={() => {
          if (onProvidersMutate !== undefined) {
            void onProvidersMutate();
          }
        }}
      />

      <ConfirmDialog
        open={confirmOpen}
        onOpenChange={setConfirmOpen}
        title="Delete Provider"
        description="Are you sure you want to delete this provider? This action cannot be undone."
        confirmLabel="Delete"
        variant="destructive"
        onConfirm={executeDelete}
      />
    </div>
  );
}
