import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ChevronLeft, ChevronRight, Plus, Pencil, Trash2, Download } from 'lucide-react';
import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch } from '@/lib/api';
import { FleetTabHeading } from '@/components/fleet/FleetEmptyState';
import type { CveSuppression, TriageStatus } from '@/types/security';
import {
  TRIAGE_STATUS_OPTIONS,
  TRIAGE_JUSTIFICATION_OPTIONS,
  TRIAGE_STATUS_HINT,
  openVexRequiresJustification,
  justificationForStatus,
  triageStatusLabel,
  triageJustificationLabel,
} from '@/lib/triage';
import { useAuth } from '@/context/AuthContext';

const CVE_ID_RE = /^(CVE-\d{4}-\d{4,}|GHSA-[\w-]{14,})$/;
const PAGE_SIZE = 8;

interface SuppressionFormState {
  cveId: string;
  pkgName: string;
  imagePattern: string;
  reason: string;
  expiresInDays: string;
  status: TriageStatus;
  justification: string;
}

const EMPTY_FORM: SuppressionFormState = {
  cveId: '',
  pkgName: '',
  imagePattern: '',
  reason: '',
  expiresInDays: '',
  status: 'accepted',
  justification: '',
};

interface SuppressionsPanelProps {
  isReplica: boolean;
}

export function SuppressionsPanel({ isReplica }: SuppressionsPanelProps) {
  const { can } = useAuth();
  const canRead = can('stack:read');
  const canManage = can('stack:edit');
  const [rows, setRows] = useState<CveSuppression[]>([]);
  const [loading, setLoading] = useState(true);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [form, setForm] = useState<SuppressionFormState>(EMPTY_FORM);
  // The row being edited, or null when the dialog is creating a new suppression.
  const [editRow, setEditRow] = useState<CveSuppression | null>(null);
  const [saving, setSaving] = useState(false);
  const [deleteRow, setDeleteRow] = useState<CveSuppression | null>(null);
  const [page, setPage] = useState(0);

  const load = useCallback(async () => {
    try {
      const res = await apiFetch('/security/suppressions', { localOnly: true });
      if (!res.ok) throw new Error('Failed to load suppressions');
      const data = await res.json();
      setRows(Array.isArray(data) ? data : []);
    } catch (err) {
      console.error('Failed to load suppressions:', err);
      toast.error('Failed to load suppressions');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    load();
  }, [load]);

  const totalPages = Math.max(1, Math.ceil(rows.length / PAGE_SIZE));
  const safePage = Math.min(page, totalPages - 1);
  const pageItems = rows.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
  const needsPagination = rows.length > PAGE_SIZE;

  const openCreate = () => {
    setEditRow(null);
    setForm(EMPTY_FORM);
    setDialogOpen(true);
  };

  const openEdit = (row: CveSuppression) => {
    setEditRow(row);
    setForm({
      cveId: row.cve_id,
      pkgName: row.pkg_name ?? '',
      imagePattern: row.image_pattern ?? '',
      reason: row.reason,
      // Recompute the expiry as days from now so the same control edits it; blank
      // means "no expiry". The CVE id and package scope are identity, not editable.
      expiresInDays: row.expires_at === null ? '' : String(Math.max(1, Math.ceil((row.expires_at - Date.now()) / 86_400_000))),
      status: row.status ?? 'accepted',
      justification: row.justification ?? '',
    });
    setDialogOpen(true);
  };

  const handleStatusChange = (status: TriageStatus) => {
    setForm((f) => ({ ...f, status, justification: justificationForStatus(status, f.justification) }));
  };

  const handleSave = async () => {
    // CVE id and package scope identify a suppression, so they are only validated
    // and sent on create; the edit endpoint updates reason, image pattern, expiry, status, and justification.
    const cveId = form.cveId.trim();
    if (!editRow && !CVE_ID_RE.test(cveId)) {
      toast.error('CVE must look like CVE-YYYY-NNNN or GHSA-xxxx-xxxx-xxxx.');
      return;
    }
    const reason = form.reason.trim();
    if (!reason) {
      toast.error('A reason is required.');
      return;
    }
    const justification = form.justification.trim();
    if (openVexRequiresJustification(form.status) && !justification) {
      toast.error('An OpenVEX justification is required for this triage decision.');
      return;
    }
    let expiresAt: number | null = null;
    const days = form.expiresInDays.trim();
    if (days) {
      const n = Number(days);
      if (!Number.isFinite(n) || n <= 0) {
        toast.error('Expiry must be a positive number of days or blank.');
        return;
      }
      expiresAt = Date.now() + n * 24 * 60 * 60 * 1000;
    }
    const sharedBody = {
      image_pattern: form.imagePattern.trim() || null,
      reason,
      expires_at: expiresAt,
      status: form.status,
      justification: openVexRequiresJustification(form.status) ? (justification || null) : null,
    };
    setSaving(true);
    try {
      const res = editRow
        ? await apiFetch(`/security/suppressions/${editRow.id}`, {
            method: 'PUT',
            localOnly: true,
            body: JSON.stringify(sharedBody),
          })
        : await apiFetch('/security/suppressions', {
            method: 'POST',
            localOnly: true,
            body: JSON.stringify({
              cve_id: cveId,
              pkg_name: form.pkgName.trim() || null,
              ...sharedBody,
            }),
          });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body?.error || `Failed to ${editRow ? 'update' : 'create'} suppression`);
      }
      toast.success(editRow ? 'Suppression updated' : 'Suppression created');
      setDialogOpen(false);
      await load();
    } catch (err) {
      toast.error((err as Error)?.message || `Failed to ${editRow ? 'update' : 'create'} suppression`);
    } finally {
      setSaving(false);
    }
  };

  const handleDelete = async () => {
    if (!deleteRow) return;
    try {
      const res = await apiFetch(`/security/suppressions/${deleteRow.id}`, {
        method: 'DELETE',
        localOnly: true,
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body?.error || 'Failed to delete suppression');
      }
      toast.success('Suppression removed');
      await load();
    } catch (err) {
      toast.error((err as Error)?.message || 'Failed to delete suppression');
    } finally {
      setDeleteRow(null);
    }
  };

  const formatExpiry = (row: CveSuppression): string => {
    if (row.expires_at === null) return 'Never';
    const d = new Date(row.expires_at);
    return d.toLocaleDateString();
  };

  const handleExportVex = useCallback(async () => {
    try {
      const res = await apiFetch('/security/vex/export', { localOnly: true });
      if (!res.ok) throw new Error('Failed to export VEX');
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'sencho-fleet.openvex.json';
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(url);
    } catch (err) {
      toast.error((err as Error)?.message || 'Failed to export VEX');
    }
  }, []);

  return (
    <div className="space-y-4">
      <FleetTabHeading
        title="CVE suppressions"
        subtitle="Accept known-benign CVEs so they stop triggering alerts. Suppressions apply at read time across the fleet and never modify stored scan data."
        action={
          !isReplica && (canRead || canManage) ? (
            <div className="flex items-center gap-2">
              {canRead && (
                <Button size="sm" variant="outline" onClick={handleExportVex}>
                  <Download className="w-4 h-4 mr-1.5" />
                  Export VEX
                </Button>
              )}
              {canManage && (
                <Button size="sm" onClick={openCreate}>
                  <Plus className="w-4 h-4 mr-1.5" />
                  Add suppression
                </Button>
              )}
            </div>
          ) : undefined
        }
      />

      <div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel p-4 space-y-3">
      {needsPagination && !loading && rows.length > 0 && (
        <div className="flex items-center justify-end gap-1">
          <Button
            variant="ghost"
            size="icon"
            className="h-6 w-6"
            onClick={() => setPage(Math.max(0, safePage - 1))}
            disabled={safePage === 0}
            aria-label="Previous page"
          >
            <ChevronLeft className="w-4 h-4" strokeWidth={1.5} />
          </Button>
          <span className="text-xs font-mono tabular-nums text-stat-subtitle min-w-[3rem] text-center">
            {safePage + 1} / {totalPages}
          </span>
          <Button
            variant="ghost"
            size="icon"
            className="h-6 w-6"
            onClick={() => setPage(Math.min(totalPages - 1, safePage + 1))}
            disabled={safePage >= totalPages - 1}
            aria-label="Next page"
          >
            <ChevronRight className="w-4 h-4" strokeWidth={1.5} />
          </Button>
        </div>
      )}

      {loading && (
        <div className="space-y-2">
          <Skeleton className="h-10 w-full rounded" />
          <Skeleton className="h-10 w-full rounded" />
        </div>
      )}

      {!loading && rows.length === 0 && (
        <div className="text-center py-6 text-xs text-muted-foreground">
          No suppressions yet. Accept a CVE from any scan result to silence it fleet-wide.
        </div>
      )}

      {!loading && rows.length > 0 && (
        <ScrollArea className="h-[420px] max-md:h-auto pr-2">
          <ul className="divide-y divide-glass-border">
            {pageItems.map((row) => {
              const justLabel = triageJustificationLabel(row.justification);
              return (
                <li key={row.id} className="py-2.5 flex items-start justify-between gap-3">
                  <div className="min-w-0 flex-1 space-y-1">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className="font-mono text-xs font-medium">{row.cve_id}</span>
                      <Badge variant="outline" className="text-[10px]">
                        {triageStatusLabel(row.status)}
                      </Badge>
                      {row.pkg_name && (
                        <Badge variant="outline" className="text-[10px] font-mono">
                          {row.pkg_name}
                        </Badge>
                      )}
                      {row.image_pattern && (
                        <Badge variant="outline" className="text-[10px] font-mono truncate max-w-[220px]">
                          {row.image_pattern}
                        </Badge>
                      )}
                      {!row.active && (
                        <Badge variant="secondary" className="text-[10px]">expired</Badge>
                      )}
                      {row.replicated_from_control === 1 && (
                        <Badge variant="secondary" className="text-[10px]">replicated</Badge>
                      )}
                    </div>
                    <div className="text-xs text-muted-foreground line-clamp-2">{row.reason}</div>
                    {justLabel && (
                      <div className="text-[11px] font-mono text-stat-subtitle">
                        Justification: {justLabel}
                      </div>
                    )}
                    <div className="text-[11px] font-mono text-stat-subtitle">
                      by {row.created_by} - expires {formatExpiry(row)}
                    </div>
                  </div>
                  {canManage && !isReplica && row.replicated_from_control === 0 && (
                    <div className="flex items-center gap-1 shrink-0">
                      <TooltipProvider>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 text-stat-subtitle hover:text-stat-value"
                              onClick={() => openEdit(row)}
                            >
                              <Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent>Edit suppression</TooltipContent>
                        </Tooltip>
                      </TooltipProvider>
                      <TooltipProvider>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 text-destructive/60 hover:bg-destructive hover:text-destructive-foreground"
                              onClick={() => setDeleteRow(row)}
                            >
                              <Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent>Remove suppression</TooltipContent>
                        </Tooltip>
                      </TooltipProvider>
                    </div>
                  )}
                </li>
              );
            })}
          </ul>
        </ScrollArea>
      )}
      </div>

      <Modal open={dialogOpen} onOpenChange={setDialogOpen} size="md">
        <ModalHeader
          kicker={editRow ? 'SUPPRESSIONS · EDIT' : 'SUPPRESSIONS · NEW'}
          title={editRow ? 'Edit suppression' : 'New suppression'}
          description={editRow
            ? 'Update the triage decision, reason, image scope, or expiry. The CVE and package scope are fixed.'
            : 'Record a triage decision for a CVE so it stops triggering alerts across the fleet.'}
        />
        <ModalBody>
          <div className="space-y-2">
            <Label htmlFor="s-cve">CVE or advisory ID</Label>
            <Input
              id="s-cve"
              placeholder="CVE-2024-12345 or GHSA-xxxx-xxxx-xxxx"
              value={form.cveId}
              disabled={!!editRow}
              onChange={(e) => setForm({ ...form, cveId: e.target.value })}
            />
          </div>
          <div className="space-y-2">
            <Label htmlFor="s-pkg">Package (optional)</Label>
            <Input
              id="s-pkg"
              placeholder="e.g. openssl (leave blank to match every package)"
              value={form.pkgName}
              disabled={!!editRow}
              onChange={(e) => setForm({ ...form, pkgName: e.target.value })}
            />
          </div>
          <div className="space-y-2">
            <Label htmlFor="s-image">Image pattern (optional)</Label>
            <Input
              id="s-image"
              placeholder="e.g. registry.internal/* (leave blank for all images)"
              value={form.imagePattern}
              onChange={(e) => setForm({ ...form, imagePattern: e.target.value })}
            />
          </div>
          <div className="space-y-2">
            <Label htmlFor="s-status">Triage decision</Label>
            <Select value={form.status} onValueChange={(v) => handleStatusChange(v as TriageStatus)}>
              <SelectTrigger id="s-status" aria-label="Triage decision" className="w-full">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {TRIAGE_STATUS_OPTIONS.map((o) => (
                  <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
                ))}
              </SelectContent>
            </Select>
            <p className="text-xs text-muted-foreground">
              {TRIAGE_STATUS_HINT}
            </p>
          </div>
          {openVexRequiresJustification(form.status) && (
            <div className="space-y-2">
              <Label htmlFor="s-justification">OpenVEX justification</Label>
              <Select
                value={form.justification}
                onValueChange={(v) => setForm({ ...form, justification: v })}
              >
                <SelectTrigger id="s-justification" aria-label="OpenVEX justification" className="w-full">
                  <SelectValue placeholder="Select a justification" />
                </SelectTrigger>
                <SelectContent>
                  {TRIAGE_JUSTIFICATION_OPTIONS.map((o) => (
                    <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          )}
          <div className="space-y-2">
            <Label htmlFor="s-reason">Reason</Label>
            <textarea
              id="s-reason"
              className="flex min-h-[72px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
              placeholder="Why is this CVE safe to accept?"
              value={form.reason}
              onChange={(e) => setForm({ ...form, reason: e.target.value })}
            />
          </div>
          <div className="space-y-2">
            <Label htmlFor="s-expiry">Expires in (days, optional)</Label>
            <Input
              id="s-expiry"
              type="number"
              min="1"
              placeholder="Leave blank for no expiry"
              value={form.expiresInDays}
              onChange={(e) => setForm({ ...form, expiresInDays: e.target.value })}
            />
          </div>
        </ModalBody>
        <ModalFooter
          secondary={
            <Button variant="outline" size="sm" onClick={() => setDialogOpen(false)} disabled={saving}>
              Cancel
            </Button>
          }
          primary={
            <Button size="sm" onClick={handleSave} disabled={saving}>
              {saving ? 'Saving...' : editRow ? 'Save changes' : 'Create'}
            </Button>
          }
        />
      </Modal>

      <ConfirmModal
        open={deleteRow !== null}
        onOpenChange={(open) => !open && setDeleteRow(null)}
        variant="destructive"
        kicker="SUPPRESSIONS · REMOVE · IRREVERSIBLE"
        title="Remove suppression"
        confirmLabel="Remove"
        onConfirm={handleDelete}
      >
        <p className="text-sm text-stat-subtitle">
          Future scan results will surface <span className="font-mono font-medium text-stat-value">{deleteRow?.cve_id}</span> again wherever it applies.
        </p>
      </ConfirmModal>
    </div>
  );
}
