// @File: frontend/src/pages/admin/cms/ApiHubProviderCard.tsx
// @Desc: Detailed provider card used inside the three API Hub category
//        pages (integrations / notifications / payments). Exposes full
//        credential detail, docs link, the live-test button, and an
//        inline editor for updating credentials via the backend API.
//
// Security notes:
//   * Secret values are never echoed back by the server — only a masked
//     preview is shown. The edit form starts with empty inputs and
//     writes through the admin-only PUT endpoint over HTTPS.
//   * Empty values clear an existing override so the provider falls
//     back to the deploy-baked environment variable (if any).
import { useCallback, useEffect, useMemo, useState } from "react";
import {
  RefreshCw,
  Loader2,
  CheckCircle2,
  AlertTriangle,
  XCircle,
  CircleDashed,
  ExternalLink,
  KeyRound,
  Pencil,
  Save,
  RotateCcw,
  Eye,
  EyeOff,
  Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { Badge } from "./ui";
import {
  testProvider,
  statusTone,
  statusLabel,
  formatRelativeTimestamp,
  updateProviderCredentials,
  deleteProviderCredential,
  type ProviderStatus,
  type ProviderStatusValue,
  type ProviderCredentialHint,
  type CredentialUpdate,
} from "@/lib/apiHub";

function StatusIcon({ status }: { status: ProviderStatusValue }) {
  switch (status) {
    case "connected":
      return <CheckCircle2 className="w-5 h-5 text-emerald-600" />;
    case "configured":
      return <AlertTriangle className="w-5 h-5 text-amber-500" />;
    case "error":
      return <XCircle className="w-5 h-5 text-red-600" />;
    default:
      return <CircleDashed className="w-5 h-5 text-slate-400" />;
  }
}

function sourceBadge(source: ProviderCredentialHint["source"]) {
  switch (source) {
    case "db":
      return <Badge tone="blue">Custom</Badge>;
    case "env":
      return <Badge tone="slate">Env</Badge>;
    default:
      return <Badge tone="yellow">Missing</Badge>;
  }
}

interface EditorRowProps {
  credential: ProviderCredentialHint;
  draftValue: string;
  showValue: boolean;
  onChange: (value: string) => void;
  onToggleShow: () => void;
  onClear: () => void;
  busy: boolean;
}

function EditorRow({
  credential,
  draftValue,
  showValue,
  onChange,
  onToggleShow,
  onClear,
  busy,
}: EditorRowProps) {
  const isSecret = credential.secret !== false; // default true
  return (
    <div className="p-3 rounded-lg border border-slate-200 bg-white">
      <div className="flex items-center justify-between gap-2 flex-wrap">
        <div className="min-w-0">
          <div className="text-sm font-semibold text-slate-900">
            {credential.label || credential.name}
          </div>
          <div className="font-mono text-[11px] text-slate-500">
            {credential.name}
          </div>
        </div>
        <div className="flex items-center gap-2">
          {credential.required ? (
            <Badge tone="red">Required</Badge>
          ) : (
            <Badge tone="slate">Optional</Badge>
          )}
          {sourceBadge(credential.source)}
        </div>
      </div>

      <div className="mt-2 flex items-stretch gap-2">
        <div className="relative flex-1">
          <input
            type={isSecret && !showValue ? "password" : "text"}
            value={draftValue}
            onChange={(e) => onChange(e.target.value)}
            placeholder={
              credential.present
                ? `Leave blank to keep current value (${credential.masked_value ?? "●●●"})`
                : credential.hint || "Enter a value"
            }
            disabled={busy}
            className="w-full px-3 py-2 pr-10 rounded-lg border border-slate-300 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-[#0b6e3e]/30 focus:border-[#0b6e3e] disabled:bg-slate-50"
            autoComplete="off"
            spellCheck={false}
          />
          {isSecret && (
            <button
              type="button"
              onClick={onToggleShow}
              className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-slate-500 hover:text-slate-800"
              title={showValue ? "Hide value" : "Show value"}
              tabIndex={-1}
            >
              {showValue ? (
                <EyeOff className="w-4 h-4" />
              ) : (
                <Eye className="w-4 h-4" />
              )}
            </button>
          )}
        </div>
        <button
          type="button"
          onClick={onClear}
          disabled={busy || (!credential.present && !draftValue)}
          className="inline-flex items-center gap-1 px-3 py-2 rounded-lg border border-slate-300 text-slate-700 text-xs font-semibold hover:bg-slate-50 transition disabled:opacity-40 disabled:cursor-not-allowed"
          title={
            credential.source === "db"
              ? "Clear the stored override (falls back to env var)"
              : "Clear this field"
          }
        >
          <Trash2 className="w-3.5 h-3.5" />
          Clear
        </button>
      </div>

      {credential.hint && (
        <p className="mt-1.5 text-[11px] text-slate-500">{credential.hint}</p>
      )}
    </div>
  );
}

export default function ApiHubProviderCard({
  provider,
  onTested,
  onUpdated,
}: {
  provider: ProviderStatus;
  onTested: () => void;
  onUpdated?: (next: ProviderStatus) => void;
}) {
  const [testing, setTesting] = useState(false);
  const [editing, setEditing] = useState(false);
  const [saving, setSaving] = useState(false);
  const [clearingNames, setClearingNames] = useState<Set<string>>(new Set());
  const [drafts, setDrafts] = useState<Record<string, string>>({});
  const [shown, setShown] = useState<Record<string, boolean>>({});
  const tone = statusTone(provider.status);

  // Reset drafts whenever the provider snapshot changes (e.g. after save).
  useEffect(() => {
    setDrafts(
      Object.fromEntries(provider.credentials.map((c) => [c.name, ""])),
    );
    setShown({});
  }, [provider]);

  const dirty = useMemo(
    () => Object.values(drafts).some((v) => v.length > 0),
    [drafts],
  );

  const handleTest = useCallback(async () => {
    if (!provider.test_supported) {
      toast.info(`${provider.name}: live testing is not supported yet.`);
      return;
    }
    setTesting(true);
    try {
      const result = await testProvider(provider.id);
      if (result.ok) {
        toast.success(
          `${provider.name}: ${result.message}` +
            (result.latency_ms ? ` (${result.latency_ms} ms)` : ""),
        );
      } else {
        const ipHint = result.outbound_ip
          ? ` Outbound IP: ${result.outbound_ip}.`
          : "";
        toast.error(`${provider.name}: ${result.message}${ipHint}`);
      }
      onTested();
    } catch (err) {
      toast.error(
        err instanceof Error ? err.message : "Failed to run provider test.",
      );
    } finally {
      setTesting(false);
    }
  }, [provider.id, provider.name, provider.test_supported, onTested]);

  const handleSave = useCallback(async () => {
    // Only submit credentials the admin actually typed something into;
    // empty strings would otherwise clear values the admin didn't intend
    // to touch.
    const payload: CredentialUpdate[] = Object.entries(drafts)
      .filter(([, v]) => v.trim().length > 0)
      .map(([name, value]) => ({ name, value: value.trim() }));
    if (payload.length === 0) {
      toast.info("No changes to save.");
      return;
    }
    setSaving(true);
    try {
      const next = await updateProviderCredentials(provider.id, payload);
      toast.success(`${provider.name}: credentials updated.`);
      setEditing(false);
      onUpdated?.(next);
      onTested(); // triggers parent refresh
    } catch (err) {
      toast.error(
        err instanceof Error ? err.message : "Failed to update credentials.",
      );
    } finally {
      setSaving(false);
    }
  }, [drafts, provider.id, provider.name, onTested, onUpdated]);

  const handleClear = useCallback(
    async (cred: ProviderCredentialHint) => {
      // If the admin was just editing the draft, clearing the input is
      // enough — no server call required.
      if (drafts[cred.name]) {
        setDrafts((prev) => ({ ...prev, [cred.name]: "" }));
        return;
      }
      // Nothing stored and nothing drafted — bail out quietly.
      if (cred.source !== "db") {
        toast.info(
          cred.source === "env"
            ? "This value comes from an environment variable and can't be cleared from the UI."
            : "Nothing to clear.",
        );
        return;
      }
      setClearingNames((prev) => new Set(prev).add(cred.name));
      try {
        const next = await deleteProviderCredential(provider.id, cred.name);
        toast.success(`${provider.name}: ${cred.name} cleared.`);
        onUpdated?.(next);
        onTested();
      } catch (err) {
        toast.error(
          err instanceof Error ? err.message : "Failed to clear credential.",
        );
      } finally {
        setClearingNames((prev) => {
          const copy = new Set(prev);
          copy.delete(cred.name);
          return copy;
        });
      }
    },
    [drafts, provider.id, provider.name, onTested, onUpdated],
  );

  return (
    <div className="border border-slate-200 rounded-2xl bg-white overflow-hidden">
      <div className="p-5 border-b border-slate-100 flex items-start justify-between gap-4">
        <div className="flex items-start gap-3 min-w-0">
          <div className="w-10 h-10 rounded-xl bg-slate-100 flex items-center justify-center shrink-0">
            <StatusIcon status={provider.status} />
          </div>
          <div className="min-w-0">
            <div className="flex items-center gap-2 flex-wrap">
              <h3 className="font-bold text-slate-900 text-base">
                {provider.name}
              </h3>
              <Badge tone={tone}>{statusLabel(provider.status)}</Badge>
              {provider.sandbox_mode && <Badge tone="blue">Sandbox</Badge>}
            </div>
            <p className="text-sm text-slate-500 mt-1">{provider.description}</p>
            <p className="text-xs text-slate-600 mt-1.5 italic">
              {provider.status_message}
            </p>
          </div>
        </div>
        <div className="shrink-0 flex flex-col gap-2">
          <button
            type="button"
            onClick={handleTest}
            disabled={testing || !provider.test_supported}
            className="inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg bg-[#0b6e3e] text-white text-xs font-semibold hover:bg-[#0a8048] transition disabled:opacity-50 disabled:cursor-not-allowed min-w-[140px]"
            title={
              provider.test_supported
                ? "Run a live connectivity test"
                : "Live testing is not supported for this provider"
            }
          >
            {testing ? (
              <Loader2 className="w-3.5 h-3.5 animate-spin" />
            ) : (
              <RefreshCw className="w-3.5 h-3.5" />
            )}
            {testing ? "Testing…" : "Test connection"}
          </button>
          <button
            type="button"
            onClick={() => setEditing((e) => !e)}
            className="inline-flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg border border-slate-300 text-slate-700 text-xs font-semibold hover:bg-slate-50 transition min-w-[140px]"
          >
            {editing ? (
              <>
                <RotateCcw className="w-3.5 h-3.5" />
                Cancel edit
              </>
            ) : (
              <>
                <Pencil className="w-3.5 h-3.5" />
                Edit credentials
              </>
            )}
          </button>
          {provider.docs_url && (
            <a
              href={provider.docs_url}
              target="_blank"
              rel="noreferrer"
              className="inline-flex items-center justify-center gap-1 px-3 py-2 rounded-lg border border-slate-300 text-slate-700 text-xs font-semibold hover:bg-slate-50 transition min-w-[140px]"
            >
              Docs
              <ExternalLink className="w-3 h-3" />
            </a>
          )}
        </div>
      </div>

      <div className="p-5 space-y-4">
        <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
          <div className="p-3 rounded-lg bg-slate-50 border border-slate-200">
            <div className="text-[11px] font-semibold text-slate-500 uppercase tracking-wider">
              Last tested
            </div>
            <div className="text-sm font-semibold text-slate-900 mt-1">
              {formatRelativeTimestamp(provider.last_checked_at)}
            </div>
          </div>
          <div className="p-3 rounded-lg bg-slate-50 border border-slate-200">
            <div className="text-[11px] font-semibold text-slate-500 uppercase tracking-wider">
              Latency
            </div>
            <div className="text-sm font-semibold text-slate-900 mt-1">
              {provider.latency_ms !== null ? `${provider.latency_ms} ms` : "—"}
            </div>
          </div>
          <div className="p-3 rounded-lg bg-slate-50 border border-slate-200">
            <div className="text-[11px] font-semibold text-slate-500 uppercase tracking-wider">
              Enabled
            </div>
            <div className="text-sm font-semibold text-slate-900 mt-1">
              {provider.enabled === null
                ? "—"
                : provider.enabled
                ? "Yes"
                : "No"}
            </div>
          </div>
        </div>

        <div>
          <div className="flex items-center justify-between gap-2 mb-2">
            <div className="flex items-center gap-2 text-[11px] font-semibold text-slate-500 uppercase tracking-wider">
              <KeyRound className="w-3.5 h-3.5" />
              Credentials
            </div>
            {editing && dirty && (
              <div className="text-[11px] text-amber-600 font-semibold">
                Unsaved changes
              </div>
            )}
          </div>

          {!editing ? (
            <div className="border border-slate-200 rounded-lg overflow-hidden">
              <table className="w-full text-sm">
                <thead className="bg-slate-50 text-slate-600 text-xs uppercase">
                  <tr>
                    <th className="text-left font-semibold px-3 py-2">Field</th>
                    <th className="text-left font-semibold px-3 py-2">Value</th>
                    <th className="text-left font-semibold px-3 py-2">
                      Required
                    </th>
                    <th className="text-left font-semibold px-3 py-2">
                      Source
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {provider.credentials.map((c) => (
                    <tr key={c.name} className="border-t border-slate-100">
                      <td className="px-3 py-2">
                        <div className="text-slate-900 font-semibold text-xs">
                          {c.label || c.name}
                        </div>
                        <div className="font-mono text-[11px] text-slate-500">
                          {c.name}
                        </div>
                      </td>
                      <td className="px-3 py-2 font-mono text-xs text-slate-600">
                        {c.present ? c.masked_value ?? "●●●" : "—"}
                      </td>
                      <td className="px-3 py-2">
                        {c.required ? (
                          <Badge tone="red">Required</Badge>
                        ) : (
                          <Badge tone="slate">Optional</Badge>
                        )}
                      </td>
                      <td className="px-3 py-2">{sourceBadge(c.source)}</td>
                    </tr>
                  ))}
                  {provider.credentials.length === 0 && (
                    <tr>
                      <td
                        colSpan={4}
                        className="px-3 py-3 text-center text-slate-500 text-sm"
                      >
                        No credentials required.
                      </td>
                    </tr>
                  )}
                </tbody>
              </table>
            </div>
          ) : (
            <div className="space-y-2">
              {provider.credentials.map((c) => (
                <EditorRow
                  key={c.name}
                  credential={c}
                  draftValue={drafts[c.name] ?? ""}
                  showValue={!!shown[c.name]}
                  onChange={(v) =>
                    setDrafts((prev) => ({ ...prev, [c.name]: v }))
                  }
                  onToggleShow={() =>
                    setShown((prev) => ({ ...prev, [c.name]: !prev[c.name] }))
                  }
                  onClear={() => handleClear(c)}
                  busy={saving || clearingNames.has(c.name)}
                />
              ))}
              <div className="flex items-center justify-end gap-2 pt-1">
                <button
                  type="button"
                  onClick={() => setEditing(false)}
                  disabled={saving}
                  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg border border-slate-300 text-slate-700 text-xs font-semibold hover:bg-slate-50 transition disabled:opacity-50"
                >
                  Cancel
                </button>
                <button
                  type="button"
                  onClick={handleSave}
                  disabled={saving || !dirty}
                  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg bg-[#0b6e3e] text-white text-xs font-semibold hover:bg-[#0a8048] transition disabled:opacity-50 disabled:cursor-not-allowed"
                >
                  {saving ? (
                    <Loader2 className="w-3.5 h-3.5 animate-spin" />
                  ) : (
                    <Save className="w-3.5 h-3.5" />
                  )}
                  {saving ? "Saving…" : "Save credentials"}
                </button>
              </div>
            </div>
          )}

          <p className="text-[11px] text-slate-500 mt-2">
            Values marked <span className="font-semibold">Custom</span> are
            stored encrypted in Atoms Cloud and take effect immediately.
            Leaving a field blank keeps the current value. Clearing a custom
            value falls back to the deploy-baked environment variable, if
            present.
          </p>
        </div>
      </div>
    </div>
  );
}