import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { PermissionGrant } from '@pig/core';
import { Check, ExternalLink, FileCheck2, RefreshCw, X } from 'lucide-react';
import { SourcedValue, type SourcedFact } from '@/components/SourcedValue';
import {
  Badge,
  Card,
  CardContent,
  CardHeader,
  CardTitle,
  EmptyState,
  Label,
  Skeleton,
} from '@/components/ui';
import { PageHeader } from '@/components/ui/page-header';
import { Button } from '@/components/ui/button';
import { get, patch, relativeTime } from '@/lib/api';
import { can } from '@/lib/permissions';
import { usePageTitle } from '@/lib/title';
import { toast } from 'sonner';

interface ReviewItem {
  fact: SourcedFact;
  accountName: string | null;
  contactName: string | null;
}

interface ReviewResponse {
  facts: ReviewItem[];
}

interface ReviewIdentity {
  permissions: PermissionGrant[];
}

interface DecisionResponse {
  fact: SourcedFact;
  recordUpdated: false;
}

function humanise(value: string): string {
  return value.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
}

function safeSourceUrl(value: string | null): string | null {
  if (!value) return null;
  try {
    const url = new URL(value);
    return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : null;
  } catch {
    return null;
  }
}

function evidenceSummary(evidence: Record<string, unknown> | null): string {
  if (!evidence) return 'No evidence excerpt was recorded.';
  for (const key of ['excerpt', 'quote', 'summary', 'snippet', 'reason']) {
    const value = evidence[key];
    if (typeof value === 'string' && value.trim()) return value.trim();
  }
  return 'Structured evidence is attached. Open the provenance marker for details.';
}

export function FactReview() {
  usePageTitle('Fact review');
  const queryClient = useQueryClient();
  const factsQuery = useQuery({
    queryKey: ['facts', 'proposed'],
    queryFn: () => get<ReviewResponse>('/api/facts?status=proposed'),
  });
  const { data: me } = useQuery({
    queryKey: ['me'],
    queryFn: () => get<ReviewIdentity>('/api/me'),
  });
  const mayReview = can(me, 'data:import', 'research');

  const decision = useMutation({
    mutationFn: ({ id, status }: { id: string; status: 'approved' | 'dismissed' }) =>
      patch<DecisionResponse>(`/api/facts/${id}/decision`, { status }),
    onSuccess: async (_result, variables) => {
      toast.success(
        variables.status === 'approved' ? 'Evidence accepted' : 'Claim dismissed',
        {
          description:
            variables.status === 'approved'
              ? 'Recorded as reviewed. Applying it to the record is a separate, field-aware step.'
              : 'It will not be proposed again from the same evidence.',
        },
      );
      await queryClient.invalidateQueries({ queryKey: ['facts', 'proposed'] });
    },
  });

  const items = factsQuery.data?.facts ?? [];

  return (
    <div className="space-y-6">
      <PageHeader
        title="Fact review"
        description="Inspect provenance, decide whether the evidence is credible, and keep uncertain claims out of record truth."
        /* Warning only while the queue holds work: a cleared queue is nothing
           to do, and a green badge on every visit teaches people to skip it. */
        actions={
          <Badge tone={items.length > 0 ? 'warning' : 'neutral'} aria-live="polite">
            {items.length} awaiting review
          </Badge>
        }
      />

      <Card className="overflow-hidden">
        <CardContent className="flex gap-3 p-4 sm:p-5">
          <FileCheck2 className="mt-0.5 size-5 shrink-0 text-muted" aria-hidden />
          <div className="min-w-0">
            <p className="font-medium">Approval validates evidence only. It does not write a CRM field.</p>
            <p className="mt-1 text-sm text-muted">
              Approved facts remain separate from accounts and contacts. No value is overwritten
              until PIG has a field-aware applicator with conflict handling.
            </p>
          </div>
        </CardContent>
      </Card>

      {decision.error ? (
        <div className="rounded-lg border border-danger/30 bg-danger/10 p-3 text-sm text-danger" role="alert">
          {decision.error instanceof Error ? decision.error.message : 'The review could not be saved.'}
        </div>
      ) : null}

      {factsQuery.isLoading ? <ReviewSkeleton /> : null}

      {factsQuery.error ? (
        <Card>
          <EmptyState
            title="Could not load the review queue"
            description={
              factsQuery.error instanceof Error
                ? factsQuery.error.message
                : 'The queue could not be read.'
            }
            // The description used to BE the instruction ("Try again.") with no
            // control to carry it out. Twelve of fifteen routes offer the
            // button; this was one of the three that did not.
            action={
              <Button type="button" variant="outline" onClick={() => void factsQuery.refetch()}>
                <RefreshCw aria-hidden />
                Try again
              </Button>
            }
          />
        </Card>
      ) : null}

      {!factsQuery.isLoading && !factsQuery.error && items.length === 0 ? (
        <Card>
          <EmptyState
            icon={<Check className="size-8 text-positive" aria-hidden />}
            title="The queue is clear"
            description="Piggy has no proposed facts waiting for a human decision."
          />
        </Card>
      ) : null}

      {items.length > 0 ? (
        <div className="grid gap-3 xl:grid-cols-2">
          {items.map((item) => {
            const busy = decision.isPending && decision.variables?.id === item.fact.id;
            return (
              <ReviewCard
                key={item.fact.id}
                item={item}
                busy={busy}
                mayReview={mayReview}
                onDecision={(status) => decision.mutate({ id: item.fact.id, status })}
              />
            );
          })}
        </div>
      ) : null}
    </div>
  );
}

function ReviewCard({
  item,
  busy,
  mayReview,
  onDecision,
}: {
  item: ReviewItem;
  busy: boolean;
  mayReview: boolean;
  onDecision(status: 'approved' | 'dismissed'): void;
}) {
  const { fact } = item;
  const subject = item.contactName ?? item.accountName ?? 'Unlinked record';
  const sourceUrl = safeSourceUrl(fact.sourceUrl);
  const hasEvidence = Boolean(sourceUrl || (fact.evidence && Object.keys(fact.evidence).length > 0));

  return (
    /*
      A column that fills its grid cell, so the decision row sits on the floor
      of the card rather than wherever the evidence above it happened to end.
      In a two-up row of unequal evidence the two Approve buttons were 22px
      apart with 39px of dead space under one and 61px under the other, which
      reads as an unfinished layout on the page whose job is a decision.
    */
    <Card className="flex min-w-0 flex-col">
      <CardHeader className="gap-3">
        <div className="flex min-w-0 items-start justify-between gap-3">
          <div className="min-w-0">
            <p className="truncate text-sm font-medium">{subject}</p>
            <p className="mt-0.5 text-xs text-muted">
              {humanise(fact.field)} · observed {relativeTime(fact.observedAt)}
            </p>
          </div>
          <Badge tone={fact.band === 'probable' ? 'info' : 'warning'}>
            {humanise(fact.band)}
          </Badge>
        </div>
        {/* 16px, like every other card title in the product. This was the only
            `text-lg` CardTitle left — 18px is the section-heading size the
            scale retired. */}
        <CardTitle className="min-w-0">
          <SourcedValue value={fact.value} fact={fact} />
        </CardTitle>
      </CardHeader>
      <CardContent className="flex flex-1 flex-col gap-4">
        <div className="rounded-md bg-surface-2 p-2.5">
          <Label>Evidence</Label>
          <p className="mt-1 break-words text-sm leading-relaxed">
            {evidenceSummary(fact.evidence)}
          </p>
          {sourceUrl ? (
            <a
              href={sourceUrl}
              target="_blank"
              rel="noreferrer"
              className="mt-2 inline-flex min-h-11 items-center gap-2 break-all text-sm font-medium text-accent-fg hover:underline"
            >
              Inspect source
              <ExternalLink className="size-3.5 shrink-0" aria-hidden />
            </a>
          ) : null}
        </div>

        {mayReview ? (
          <div className="mt-auto flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
            <Button
              type="button"
              variant="outline"
              className="min-h-11 sm:min-w-28"
              disabled={busy}
              onClick={() => onDecision('dismissed')}
            >
              <X aria-hidden />
              Dismiss
            </Button>
            <Button
              type="button"
              className="min-h-11 sm:min-w-28"
              disabled={busy || !hasEvidence}
              title={!hasEvidence ? 'Approval requires evidence or a source.' : undefined}
              onClick={() => onDecision('approved')}
            >
              <Check aria-hidden />
              {busy ? 'Saving…' : 'Approve evidence'}
            </Button>
          </div>
        ) : (
          <p className="mt-auto text-xs text-muted">
            Research administrators can decide proposals. You have read-only access.
          </p>
        )}
      </CardContent>
    </Card>
  );
}

function ReviewSkeleton() {
  return (
    <div className="grid gap-3 xl:grid-cols-2" aria-label="Loading fact review queue">
      {[0, 1].map((item) => (
        <Card key={item} className="p-5">
          <Skeleton className="h-4 w-32" />
          <Skeleton className="mt-4 h-7 w-2/3" />
          <Skeleton className="mt-5 h-24 w-full" />
        </Card>
      ))}
    </div>
  );
}
