'use client';

import { useEffect, useState, useCallback, useMemo } from 'react';
import { type ColumnDef } from '@tanstack/react-table';
import { formatDistanceToNow } from 'date-fns';
import { formatDate } from '@/lib/utils/format-date';
import { Download, History, Search, X } from 'lucide-react';
import { toast } from 'sonner';

import { DataTable } from '@/components/shared/data-table';
import { PageHeader } from '@/components/shared/page-header';
import { EmptyState } from '@/components/shared/empty-state';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { DatePicker } from '@/components/ui/date-picker';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { AuditLogCard } from './audit-log-card';

interface AuditEntry {
  id: string;
  userId: string | null;
  action: string;
  entityType: string;
  entityId: string | null;
  fieldChanged: string | null;
  oldValue: Record<string, unknown> | null;
  newValue: Record<string, unknown> | null;
  metadata: Record<string, unknown> | null;
  ipAddress: string | null;
  userAgent: string | null;
  severity: 'info' | 'warning' | 'error' | 'critical';
  source: 'user' | 'system' | 'auth' | 'webhook' | 'cron' | 'job';
  createdAt: string;
  actor: { id: string; email: string; name: string } | null;
}

interface AuditResponse {
  data: AuditEntry[];
  pagination: { nextCursor: { createdAt: string; id: string } | null };
}

const ACTION_COLORS: Record<string, string> = {
  create: 'bg-green-600',
  update: 'bg-blue-500',
  delete: 'bg-red-600',
  archive: 'bg-orange-500',
  restore: 'bg-teal-500',
  login: 'bg-slate-500',
  logout: 'bg-slate-400',
  permission_denied: 'bg-red-800',
  merge: 'bg-purple-500',
  revert: 'bg-amber-500',
  hard_delete: 'bg-red-900',
  request_hard_delete_code: 'bg-orange-700',
  send: 'bg-indigo-500',
  view: 'bg-gray-400',
  webhook_delivered: 'bg-emerald-500',
  webhook_failed: 'bg-amber-600',
  webhook_dead_letter: 'bg-red-700',
  webhook_retried: 'bg-indigo-600',
  job_failed: 'bg-rose-700',
  cron_run: 'bg-sky-500',
};

const SEVERITY_BADGE: Record<string, string> = {
  info: 'bg-slate-200 text-slate-800',
  warning: 'bg-amber-200 text-amber-900',
  error: 'bg-red-200 text-red-900',
  critical: 'bg-red-600 text-white',
};

const SOURCE_LABEL: Record<string, string> = {
  user: 'User',
  system: 'System',
  auth: 'Auth',
  webhook: 'Webhook',
  cron: 'Cron',
  job: 'Job',
};

// L-AU03: entity types that mutations can target but the filter dropdown
// didn't expose. Reps querying the audit log for, e.g., an email-account
// toggle (H-05 fix) couldn't pick it from the dropdown.
const ENTITY_TYPES = [
  'client',
  'interest',
  'berth',
  'document',
  'expense',
  'invoice',
  'reminder',
  'user',
  'role',
  'port',
  'setting',
  'tag',
  'webhook',
  'yacht',
  'company',
  'reservation',
  'email_account',
  'portal_session',
  'portal_user',
  'file',
];

function useDebounced<T>(value: T, ms = 300): T {
  const [v, setV] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setV(value), ms);
    return () => clearTimeout(t);
  }, [value, ms]);
  return v;
}

export function AuditLogList() {
  const [entries, setEntries] = useState<AuditEntry[]>([]);
  const [nextCursor, setNextCursor] = useState<{
    createdAt: string;
    id: string;
  } | null>(null);
  const [loading, setLoading] = useState(true);
  const [loadingMore, setLoadingMore] = useState(false);
  const [loadError, setLoadError] = useState<string | null>(null);

  // Filter state - debounce text inputs.
  const [search, setSearch] = useState('');
  const [entityType, setEntityType] = useState<string>('all');
  const [action, setAction] = useState<string>('all');
  const [severity, setSeverity] = useState<string>('all');
  const [source, setSource] = useState<string>('all');
  const [userId, setUserId] = useState('');
  const [dateFrom, setDateFrom] = useState('');
  const [dateTo, setDateTo] = useState('');
  // Per-row detail is surfaced inline via a Popover anchored to the
  // Details button (see column cell below). Lets the rep inspect the
  // full oldValue / newValue / metadata / IP / UA payload without
  // leaving the table or opening a Sheet.

  const debouncedSearch = useDebounced(search);
  const debouncedUserId = useDebounced(userId);

  const queryString = useMemo(() => {
    const params = new URLSearchParams({ limit: '50' });
    if (entityType !== 'all') params.set('entityType', entityType);
    if (action !== 'all') params.set('action', action);
    if (severity !== 'all') params.set('severity', severity);
    if (source !== 'all') params.set('source', source);
    if (debouncedSearch) params.set('search', debouncedSearch);
    if (debouncedUserId) params.set('userId', debouncedUserId);
    // Skip the date filters when From > To - the inline warning below
    // tells the user to fix it; we don't want to fire a request with a
    // useless empty range either.
    const datesValid = !(dateFrom && dateTo && dateFrom > dateTo);
    if (datesValid && dateFrom) params.set('dateFrom', new Date(dateFrom).toISOString());
    if (datesValid && dateTo) {
      const end = new Date(dateTo);
      end.setHours(23, 59, 59, 999);
      params.set('dateTo', end.toISOString());
    }
    return params.toString();
  }, [entityType, action, severity, source, debouncedSearch, debouncedUserId, dateFrom, dateTo]);

  const fetchFirstPage = useCallback(async () => {
    setLoading(true);
    setLoadError(null);
    try {
      const res = await apiFetch<AuditResponse>(`/api/v1/admin/audit?${queryString}`);
      setEntries(res.data);
      setNextCursor(res.pagination.nextCursor);
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to load audit log';
      setLoadError(msg);
      toast.error(msg);
    } finally {
      setLoading(false);
    }
  }, [queryString]);

  const loadMore = useCallback(async () => {
    if (!nextCursor) return;
    setLoadingMore(true);
    try {
      const params = new URLSearchParams(queryString);
      params.set('cursorAt', nextCursor.createdAt);
      params.set('cursorId', nextCursor.id);
      const res = await apiFetch<AuditResponse>(`/api/v1/admin/audit?${params}`);
      setEntries((prev) => [...prev, ...res.data]);
      setNextCursor(res.pagination.nextCursor);
    } catch (err) {
      toastError(err, 'Failed to load more audit entries');
    } finally {
      setLoadingMore(false);
    }
  }, [queryString, nextCursor]);

  useEffect(() => {
    // Refetch on filter change. Migrating this list to useInfiniteQuery
    // would be the proper fix but is deferred - the fetch-on-effect
    // pattern here is functionally correct and gated by the queryString
    // memo so it only fires when filters actually change.
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void fetchFirstPage();
  }, [fetchFirstPage]);

  function clearFilters() {
    setSearch('');
    setEntityType('all');
    setAction('all');
    setSeverity('all');
    setSource('all');
    setUserId('');
    setDateFrom('');
    setDateTo('');
  }

  const hasActiveFilter =
    Boolean(search) ||
    entityType !== 'all' ||
    action !== 'all' ||
    severity !== 'all' ||
    source !== 'all' ||
    Boolean(userId) ||
    Boolean(dateFrom) ||
    Boolean(dateTo);

  const dateRangeInvalid = Boolean(dateFrom && dateTo && dateFrom > dateTo);

  const columns: ColumnDef<AuditEntry, unknown>[] = [
    {
      accessorKey: 'createdAt',
      header: 'Time',
      cell: ({ row }) => (
        <div className="text-sm">
          <div>{formatDate(row.original.createdAt, 'datetime.medium')}</div>
          <div className="text-xs text-muted-foreground">
            {formatDistanceToNow(new Date(row.original.createdAt), { addSuffix: true })}
          </div>
        </div>
      ),
      size: 180,
    },
    {
      accessorKey: 'action',
      header: 'Action',
      cell: ({ row }) => {
        const verbLabel = row.original.action.replace(/_/g, ' ');
        const entityLabel = row.original.entityType.replace(/_/g, ' ');
        return (
          <div className="flex flex-col gap-1">
            <div className="flex items-center gap-1.5">
              <Badge
                className={`${ACTION_COLORS[row.original.action] ?? 'bg-gray-500'} text-white text-xs`}
              >
                {verbLabel}
              </Badge>
              {row.original.severity !== 'info' && (
                <Badge
                  className={`${SEVERITY_BADGE[row.original.severity] ?? ''} text-xs px-1.5 py-0 uppercase`}
                  variant="outline"
                >
                  {row.original.severity}
                </Badge>
              )}
            </div>
            <span className="text-xs text-muted-foreground capitalize">{entityLabel}</span>
          </div>
        );
      },
      size: 180,
    },
    {
      accessorKey: 'source',
      header: 'Source',
      cell: ({ row }) => (
        <span className="text-xs text-muted-foreground">
          {SOURCE_LABEL[row.original.source] ?? row.original.source}
        </span>
      ),
      size: 80,
    },
    {
      accessorKey: 'entityType',
      header: 'Entity',
      cell: ({ row }) => (
        <div>
          <span className="font-medium capitalize">{row.original.entityType}</span>
          {row.original.entityId ? (
            <code className="ml-2 text-xs text-muted-foreground">
              {row.original.entityId.slice(0, 8)}…
            </code>
          ) : null}
        </div>
      ),
    },
    {
      id: 'changes',
      header: 'Changes',
      cell: ({ row }) => {
        const { newValue, fieldChanged } = row.original;
        if (fieldChanged) return <span className="text-sm">{fieldChanged}</span>;
        if (newValue) {
          const keys = Object.keys(newValue);
          return (
            <span className="text-xs text-muted-foreground">
              {keys.slice(0, 3).join(', ')}
              {keys.length > 3 ? ` +${keys.length - 3} more` : ''}
            </span>
          );
        }
        return <span className="text-xs text-muted-foreground">-</span>;
      },
    },
    {
      id: 'actor',
      header: 'Actor',
      cell: ({ row }) => {
        const { actor, userId: rawId } = row.original;
        if (actor) {
          return (
            <div className="text-sm">
              <div className="font-medium">{actor.name}</div>
              <div className="text-xs text-muted-foreground">{actor.email}</div>
            </div>
          );
        }
        if (rawId) {
          return <code className="text-xs">{rawId.slice(0, 8)}…</code>;
        }
        return <span className="text-xs text-muted-foreground">system</span>;
      },
      size: 180,
    },
    {
      id: 'ip',
      header: 'IP',
      cell: ({ row }) =>
        row.original.ipAddress ? (
          <code className="text-xs text-muted-foreground">{row.original.ipAddress}</code>
        ) : (
          <span className="text-xs text-muted-foreground"> - </span>
        ),
      size: 130,
    },
    {
      id: 'details',
      header: '',
      cell: ({ row }) => {
        const e = row.original;
        const hasDetail =
          Boolean(e.oldValue) || Boolean(e.newValue) || Boolean(e.metadata) || Boolean(e.userAgent);
        if (!hasDetail) return null;
        return (
          <Popover>
            <PopoverTrigger asChild>
              <Button variant="ghost" size="sm" className="h-7 px-2 text-xs">
                Details
              </Button>
            </PopoverTrigger>
            <PopoverContent
              align="end"
              side="bottom"
              className="w-[420px] max-h-[60vh] overflow-y-auto p-3"
            >
              <div className="space-y-3 text-sm">
                <div className="space-y-0.5">
                  <p className="font-semibold capitalize">
                    {e.action.replace(/_/g, ' ')} - {e.entityType}
                  </p>
                  <p className="text-xs text-muted-foreground">
                    {formatDate(e.createdAt, 'datetime.medium')}
                    {e.actor ? ` · ${e.actor.name}` : ''}
                  </p>
                </div>
                {e.oldValue ? (
                  <details>
                    <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                      Old value
                    </summary>
                    <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
                      {JSON.stringify(e.oldValue, null, 2)}
                    </pre>
                  </details>
                ) : null}
                {e.newValue ? (
                  <details open>
                    <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                      New value
                    </summary>
                    <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
                      {JSON.stringify(e.newValue, null, 2)}
                    </pre>
                  </details>
                ) : null}
                {e.metadata ? (
                  <details>
                    <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                      Metadata
                    </summary>
                    <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
                      {JSON.stringify(e.metadata, null, 2)}
                    </pre>
                  </details>
                ) : null}
                {e.ipAddress || e.userAgent ? (
                  <dl className="grid grid-cols-[88px_1fr] gap-x-2 gap-y-1 text-xs">
                    {e.ipAddress ? (
                      <>
                        <dt className="font-semibold text-muted-foreground">IP address</dt>
                        <dd className="font-mono">{e.ipAddress}</dd>
                      </>
                    ) : null}
                    {e.userAgent ? (
                      <>
                        <dt className="font-semibold text-muted-foreground">User agent</dt>
                        <dd className="font-mono break-all">{e.userAgent}</dd>
                      </>
                    ) : null}
                  </dl>
                ) : null}
              </div>
            </PopoverContent>
          </Popover>
        );
      },
      size: 80,
    },
  ];

  return (
    <div>
      <PageHeader
        title="Audit Log"
        eyebrow="Admin"
        description="Every state change in this port - fully searchable."
        variant="gradient"
      />

      <div className="mt-4 flex flex-wrap items-end gap-x-4 gap-y-3">
        <div className="space-y-1.5">
          <Label htmlFor="audit-search" className="text-xs">
            Search
          </Label>
          <div className="relative w-72">
            <Search
              className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
              aria-hidden
            />
            <Input
              id="audit-search"
              className="pl-9 h-9"
              placeholder="entity id, entity type, action, user id…"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              data-testid="audit-search"
            />
          </div>
        </div>

        <div className="space-y-1.5">
          <Label className="text-xs">Entity</Label>
          <Select value={entityType} onValueChange={setEntityType}>
            <SelectTrigger className="w-36" data-testid="audit-entity">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All entities</SelectItem>
              {ENTITY_TYPES.map((t) => (
                <SelectItem key={t} value={t}>
                  {t.charAt(0).toUpperCase() + t.slice(1)}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1.5">
          <Label className="text-xs">Action</Label>
          <Select value={action} onValueChange={setAction}>
            <SelectTrigger className="w-44" data-testid="audit-action">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All actions</SelectItem>
              <SelectItem value="create">Create</SelectItem>
              <SelectItem value="update">Update</SelectItem>
              <SelectItem value="delete">Delete</SelectItem>
              <SelectItem value="archive">Archive</SelectItem>
              <SelectItem value="restore">Restore</SelectItem>
              <SelectItem value="merge">Merge</SelectItem>
              <SelectItem value="revert">Revert</SelectItem>
              <SelectItem value="login">Login</SelectItem>
              <SelectItem value="logout">Logout</SelectItem>
              <SelectItem value="permission_denied">Permission denied</SelectItem>
              <SelectItem value="hard_delete">Hard delete</SelectItem>
              <SelectItem value="request_hard_delete_code">Hard-delete code req</SelectItem>
              <SelectItem value="send">Send</SelectItem>
              <SelectItem value="view">View</SelectItem>
              <SelectItem value="webhook_delivered">Webhook delivered</SelectItem>
              <SelectItem value="webhook_failed">Webhook failed</SelectItem>
              <SelectItem value="webhook_dead_letter">Webhook DLQ</SelectItem>
              <SelectItem value="webhook_retried">Webhook retried</SelectItem>
              <SelectItem value="job_failed">Job failed</SelectItem>
              <SelectItem value="cron_run">Cron run</SelectItem>
              {/* L-AU02: actions that fire in the code but were missing from
                  the dropdown  -  reps couldn't filter on them. */}
              <SelectItem value="password_change">Password change</SelectItem>
              <SelectItem value="portal_invite">Portal invite</SelectItem>
              <SelectItem value="portal_activate">Portal activate</SelectItem>
              <SelectItem value="portal_password_reset_request">Portal reset req</SelectItem>
              <SelectItem value="portal_password_reset">Portal reset</SelectItem>
              <SelectItem value="revoke_invite">Revoke invite</SelectItem>
              <SelectItem value="resend_invite">Resend invite</SelectItem>
              <SelectItem value="request_gdpr_export">GDPR req</SelectItem>
              <SelectItem value="send_gdpr_export">GDPR sent</SelectItem>
              <SelectItem value="rule_evaluated">Rule evaluated</SelectItem>
              <SelectItem value="outcome_set">Outcome set</SelectItem>
              <SelectItem value="outcome_cleared">Outcome cleared</SelectItem>
              <SelectItem value="branding.logo.uploaded">Logo uploaded</SelectItem>
              <SelectItem value="branding.logo.archived">Logo archived</SelectItem>
              <SelectItem value="eoi_field_override">EOI field override</SelectItem>
              <SelectItem value="promote_to_primary">Contact promoted</SelectItem>
              <SelectItem value="eoi_spawn_yacht">EOI spawn yacht</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1.5">
          <Label className="text-xs">Severity</Label>
          <Select value={severity} onValueChange={setSeverity}>
            <SelectTrigger className="w-32" data-testid="audit-severity">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All severities</SelectItem>
              <SelectItem value="info">Info</SelectItem>
              <SelectItem value="warning">Warning</SelectItem>
              <SelectItem value="error">Error</SelectItem>
              <SelectItem value="critical">Critical</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1.5">
          <Label className="text-xs">Source</Label>
          <Select value={source} onValueChange={setSource}>
            <SelectTrigger className="w-32" data-testid="audit-source">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All sources</SelectItem>
              <SelectItem value="user">User</SelectItem>
              <SelectItem value="auth">Auth</SelectItem>
              <SelectItem value="system">System</SelectItem>
              <SelectItem value="webhook">Webhook</SelectItem>
              <SelectItem value="cron">Cron</SelectItem>
              <SelectItem value="job">Job</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="audit-user" className="text-xs">
            User id
          </Label>
          <Input
            id="audit-user"
            className="w-44 h-9"
            placeholder="exact user id"
            value={userId}
            onChange={(e) => setUserId(e.target.value)}
          />
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="audit-from" className="text-xs">
            From
          </Label>
          <DatePicker
            id="audit-from"
            className="w-52 h-9"
            value={dateFrom}
            onChange={setDateFrom}
          />
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="audit-to" className="text-xs">
            To
          </Label>
          <DatePicker id="audit-to" className="w-52 h-9" value={dateTo} onChange={setDateTo} />
        </div>

        {/* M-AU03: CSV export inherits the current filter set. The
            endpoint streams up to 10 000 rows; reps wanting deeper
            history narrow the filter first. */}
        <Button
          variant="outline"
          size="sm"
          className="ml-auto"
          onClick={() => {
            const url = new URL('/api/v1/admin/audit/export', window.location.origin);
            if (debouncedSearch) url.searchParams.set('q', debouncedSearch);
            if (entityType !== 'all') url.searchParams.set('entityType', entityType);
            if (action !== 'all') url.searchParams.set('action', action);
            if (severity !== 'all') url.searchParams.set('severity', severity);
            if (source !== 'all') url.searchParams.set('source', source);
            if (userId) url.searchParams.set('userId', userId);
            if (dateFrom) url.searchParams.set('from', dateFrom);
            if (dateTo) url.searchParams.set('to', dateTo);
            window.location.href = url.toString();
          }}
        >
          <Download className="mr-1.5 h-3 w-3" aria-hidden />
          Export CSV
        </Button>
        {hasActiveFilter ? (
          <Button variant="ghost" size="sm" onClick={clearFilters}>
            <X className="mr-1.5 h-3 w-3" />
            Clear
          </Button>
        ) : null}
      </div>

      {dateRangeInvalid && (
        <p className="mt-2 text-xs text-destructive">
          From date must be on or before To date - date filter ignored.
        </p>
      )}

      {loadError && !loading && entries.length === 0 ? (
        <div className="mt-4 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm space-y-2">
          <p className="text-destructive">Couldn&rsquo;t load audit log: {loadError}</p>
          <Button size="sm" variant="outline" onClick={() => void fetchFirstPage()}>
            Retry
          </Button>
        </div>
      ) : (
        <div className="mt-4">
          <DataTable
            columns={columns}
            data={entries}
            isLoading={loading}
            getRowId={(row) => row.id}
            cardRender={(row) => <AuditLogCard entry={row.original} />}
            virtual
            virtualHeightPx={640}
            virtualRowHeightPx={56}
            emptyState={
              <EmptyState
                icon={History}
                title="No audit log entries"
                description={
                  hasActiveFilter
                    ? 'No entries match the current filters. Try clearing them.'
                    : 'Activity will appear here once users start making changes.'
                }
              />
            }
          />
        </div>
      )}

      {nextCursor ? (
        <div className="mt-4 flex justify-center">
          <Button
            variant="outline"
            size="sm"
            disabled={loadingMore}
            onClick={() => void loadMore()}
            data-testid="audit-load-more"
          >
            {loadingMore ? 'Loading…' : 'Load more'}
          </Button>
        </div>
      ) : null}
    </div>
  );
}
