'use client';

import { useMemo, useState } from 'react';
import Link from 'next/link';
import type { Route } from 'next';
import { useRouter } from 'next/navigation';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
  ArrowLeft,
  Bell,
  CheckCircle2,
  ChevronDown,
  Clock,
  Download,
  Eye,
  FileText,
  Mail,
  Pencil,
  Send,
  Trash2,
  UserPlus,
  X,
  XCircle,
} from 'lucide-react';
import { format, formatDistanceToNowStrict } from 'date-fns';
import { toast } from 'sonner';

import { Button } from '@/components/ui/button';
import { PageHeader } from '@/components/shared/page-header';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation';
import { useConfirmation } from '@/hooks/use-confirmation';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { SigningProgress } from '@/components/documents/signing-progress';
import { ExternalEoiEditDialog } from '@/components/documents/external-eoi-edit-dialog';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';

/** Capitalize the first letter; rest stays as-is. Used for normalising
 *  free-text enum values ('signer'/'approver'/'sent'/'pending') for
 *  display without resorting to full ALL-CAPS that other surfaces use. */
function capFirst(s: string | null | undefined): string {
  if (!s) return '';
  return s.charAt(0).toUpperCase() + s.slice(1);
}

interface DetailDoc {
  id: string;
  title: string;
  status: string;
  documentType: string;
  documensoId: string | null;
  signedFileId: string | null;
  tenancyId: string | null;
  interestId: string | null;
  clientId: string | null;
  yachtId: string | null;
  companyId: string | null;
  createdAt: string;
  createdBy: string;
  isManualUpload: boolean;
  notes: string | null;
}

interface DetailSigner {
  id: string;
  signerName: string;
  signerEmail: string;
  signerRole: string;
  signingOrder: number;
  status: string;
  /** Null = never invited yet → "Send invitation" CTA.
   *  Set + status pending → "Send reminder" CTA. */
  invitedAt: string | null;
  signedAt: string | null;
  signingUrl: string | null;
}

interface DetailEvent {
  id: string;
  eventType: string;
  createdAt: string;
  signerId: string | null;
  eventData: Record<string, unknown> | null;
}

interface DetailWatcher {
  userId: string;
  addedBy: string;
  addedAt: string;
}

interface DetailLinked {
  interest: { id: string; clientName: string | null; berthLabel: string | null } | null;
  client: { id: string; fullName: string } | null;
  yacht: { id: string; name: string } | null;
  company: { id: string; name: string } | null;
}

interface DetailResponse {
  data: {
    document: DetailDoc;
    signers: DetailSigner[];
    events: DetailEvent[];
    watchers: DetailWatcher[];
    linked: DetailLinked;
  };
}

const STATUS_PILL_MAP: Record<string, StatusPillStatus> = {
  draft: 'draft',
  sent: 'sent',
  partially_signed: 'partial',
  completed: 'completed',
  signed: 'signed',
  expired: 'expired',
  cancelled: 'cancelled',
  rejected: 'rejected',
  pending: 'pending',
  declined: 'declined',
};

interface DocumentDetailProps {
  documentId: string;
  portSlug: string;
}

export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
  const router = useRouter();
  const [isCancelling, setIsCancelling] = useState(false);
  const [editingMetadata, setEditingMetadata] = useState(false);
  const { confirm, dialog: confirmDialog } = useConfirmation();

  const { data, isLoading, error } = useQuery<DetailResponse>({
    queryKey: ['document-detail', documentId],
    queryFn: () => apiFetch<DetailResponse>(`/api/v1/documents/${documentId}?detail=true`),
  });

  useRealtimeInvalidation({
    'document:updated': [['document-detail', documentId]],
    'document:sent': [['document-detail', documentId]],
    'document:completed': [['document-detail', documentId]],
    'document:cancelled': [['document-detail', documentId]],
    'document:rejected': [['document-detail', documentId]],
    'document:expired': [['document-detail', documentId]],
    'document:signer:signed': [['document-detail', documentId]],
    'document:signer:opened': [['document-detail', documentId]],
  });

  if (isLoading) {
    return (
      <div className="flex flex-col gap-4">
        <div className="h-24 animate-pulse rounded-xl bg-muted/40" />
        <div className="grid gap-4 lg:grid-cols-[2fr_1fr]">
          <div className="h-64 animate-pulse rounded-md bg-muted/40" />
          <div className="h-64 animate-pulse rounded-md bg-muted/40" />
        </div>
      </div>
    );
  }

  if (error || !data) {
    return (
      <div className="flex flex-col gap-4">
        <PageHeader
          title="Document not found"
          description="This document was deleted or you don't have access to it."
          actions={
            <Button asChild variant="outline">
              <Link href={`/${portSlug}/documents`}>
                <ArrowLeft className="mr-1.5 h-4 w-4" aria-hidden />
                Back to documents
              </Link>
            </Button>
          }
        />
      </div>
    );
  }

  const { document: doc, signers, events, watchers, linked } = data.data;

  // #67: signer-row "Send invitation" / "Send reminder" handlers used
  // to live here on the doc-detail page directly. The Signers section
  // now reuses <SigningProgress>, which owns those handlers internally
  // (calls the same /remind and /send-invitation routes). The wrappers
  // were intentionally removed in the doc-detail polish wave.

  const handleCancel = async () => {
    const ok = await confirm({
      title: 'Cancel document',
      description: 'Cancel this document? This cancels the signing request and cannot be undone.',
      confirmLabel: 'Cancel document',
    });
    if (!ok) return;
    setIsCancelling(true);
    try {
      await apiFetch(`/api/v1/documents/${documentId}/cancel`, { method: 'POST' });
      toast.success('Document cancelled');
      router.push(`/${portSlug}/documents`);
    } catch (err) {
      toastError(err);
      setIsCancelling(false);
    }
  };

  const handleEmailSignedPdf = async () => {
    try {
      const draft = await apiFetch<{
        data: { to: string[]; subject: string; attachments: Array<{ fileId: string }> };
      }>(`/api/v1/documents/${documentId}/compose-completion-email`, { method: 'POST' });
      toast.info(
        `Email composer prepared for ${draft.data.to.length} signer${draft.data.to.length === 1 ? '' : 's'} - opens in PR8 wizard`,
      );
    } catch (err) {
      toastError(err);
    }
  };

  const isInFlight = ['sent', 'partially_signed'].includes(doc.status);
  const isComplete = ['completed', 'signed'].includes(doc.status);

  // #67: linked-entity rows now show the entity TYPE + NAME (resolved
  // server-side in getDocumentDetail) so the card reads "Interest -
  // Matt Ciaccio" instead of "Interest →". Multiple linked entities
  // render as a chip row; nothing renders when there's nothing to
  // link.
  const linkedRows: Array<{ href: string; label: string; sub: string | null }> = [];
  if (doc.tenancyId) {
    linkedRows.push({
      href: `/${portSlug}/tenancies/${doc.tenancyId}`,
      label: 'Tenancy',
      sub: null,
    });
  }
  if (linked.interest) {
    linkedRows.push({
      href: `/${portSlug}/interests/${linked.interest.id}`,
      label: 'Interest',
      // Show the berth label (e.g. "A1-A3, B5-B7" or "A12") so the
      // Interest link carries distinct information from the Client
      // link rendered just below - otherwise both rows show the same
      // client name and the Interest row reads as duplicate.
      sub: linked.interest.berthLabel ?? linked.interest.clientName ?? 'No berths linked',
    });
  }
  if (linked.client) {
    linkedRows.push({
      href: `/${portSlug}/clients/${linked.client.id}`,
      label: 'Client',
      sub: linked.client.fullName,
    });
  }
  if (linked.yacht) {
    linkedRows.push({
      href: `/${portSlug}/yachts/${linked.yacht.id}`,
      label: 'Yacht',
      sub: linked.yacht.name,
    });
  }
  if (linked.company) {
    linkedRows.push({
      href: `/${portSlug}/companies/${linked.company.id}`,
      label: 'Company',
      sub: linked.company.name,
    });
  }

  return (
    <div className="flex flex-col gap-4">
      <PageHeader
        eyebrow={doc.documentType.replace(/_/g, ' ')}
        title={doc.title}
        description={`Created ${new Date(doc.createdAt).toLocaleDateString(undefined)}`}
        kpiLine={
          <>
            <StatusPill status={STATUS_PILL_MAP[doc.status] ?? 'pending'} withDot>
              {capFirst(doc.status.replace(/_/g, ' '))}
            </StatusPill>
            <span>
              {signers.filter((s) => s.status === 'signed').length}/{signers.length} signed
            </span>
            {watchers.length > 0 ? <span>{watchers.length} watching</span> : null}
          </>
        }
        actions={
          <div className="flex items-center gap-2">
            <Button asChild variant="outline" size="sm">
              <Link href={`/${portSlug}/documents`}>
                <ArrowLeft className="mr-1.5 h-4 w-4" aria-hidden /> Back
              </Link>
            </Button>
            {isComplete && doc.signedFileId ? (
              <>
                <Button asChild size="sm">
                  <Link href={`/api/v1/files/${doc.signedFileId}/download?redirect=1`}>
                    <Download className="mr-1.5 h-4 w-4" aria-hidden /> Download signed PDF
                  </Link>
                </Button>
                <Button size="sm" variant="outline" onClick={handleEmailSignedPdf}>
                  <Mail className="mr-1.5 h-4 w-4" aria-hidden /> Email signatories
                </Button>
              </>
            ) : null}
            {doc.isManualUpload && doc.documentType === 'eoi' ? (
              <Button size="sm" variant="outline" onClick={() => setEditingMetadata(true)}>
                <Pencil className="mr-1.5 h-4 w-4" aria-hidden /> Edit metadata
              </Button>
            ) : null}
            {isInFlight ? (
              <Button size="sm" variant="outline" onClick={handleCancel} disabled={isCancelling}>
                <X className="mr-1.5 h-4 w-4" /> Cancel
              </Button>
            ) : null}
          </div>
        }
        variant="gradient"
      />

      <div className="grid gap-4 lg:grid-cols-[2fr_1fr]">
        {/* Left column */}
        <div className="flex flex-col gap-4">
          <section className="rounded-md border bg-white p-4">
            <h2 className="mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
              Signers
            </h2>
            {signers.length === 0 ? (
              <p className="text-sm text-muted-foreground">No signers attached.</p>
            ) : (
              // #67 visual parity: re-use the EOI tab's <SigningProgress>
              // so the doc-detail card matches every other surface that
              // shows signer state (cleaned names, status-tinted card,
              // state-aware action button, signing-link copy, role chip).
              <SigningProgress documentId={documentId} signers={signers} />
            )}
          </section>

          {linkedRows.length > 0 ? (
            <section className="rounded-md border bg-white p-4">
              <h2 className="mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
                Linked {linkedRows.length === 1 ? 'entity' : 'entities'}
              </h2>
              <div className="flex flex-wrap gap-2">
                {linkedRows.map((row) => (
                  <Link
                    key={row.href}
                    href={row.href as Route}
                    className="inline-flex flex-col rounded-md border px-3 py-2 text-sm hover:bg-muted/50"
                  >
                    <span className="text-xs uppercase tracking-wide text-muted-foreground">
                      {row.label}
                    </span>
                    <span className="font-medium text-brand">
                      {row.sub ?? `Open ${row.label.toLowerCase()}`}
                    </span>
                  </Link>
                ))}
              </div>
            </section>
          ) : null}
        </div>

        {/* Right column */}
        <div className="flex flex-col gap-4">
          <WatchersCard documentId={documentId} watchers={watchers} />

          <ActivityCard events={events} />
        </div>
      </div>
      {confirmDialog}
      {doc.isManualUpload && doc.documentType === 'eoi' && editingMetadata ? (
        <ExternalEoiEditDialog
          open
          onOpenChange={setEditingMetadata}
          documentId={doc.id}
          initial={{
            title: doc.title,
            // Use the earliest signedAt across signers as the canonical date
            // (matches the upload service's `signedAtMoment` semantics). Falls
            // back to the doc's createdAt slice if no signer is stamped.
            signedAt: (() => {
              const stamps = signers
                .map((s) => s.signedAt)
                .filter((d): d is string => !!d)
                .sort();
              return (stamps[0] ?? doc.createdAt).slice(0, 10);
            })(),
            notes: doc.notes ?? '',
            signatories: signers.map((s) => ({
              id: s.id,
              name: s.signerName,
              email: s.signerEmail,
              role: (s.signerRole as 'client' | 'developer' | 'rep' | 'witness' | 'cc') ?? 'client',
            })),
          }}
        />
      ) : null}
    </div>
  );
}

// #67 activity panel polish: per-event icons + actor-aware copy +
// precise tooltip + reverse-chronological order. Same data the
// previous flat list rendered, just legible.
const EVENT_META: Record<
  string,
  { label: (data?: Record<string, unknown>) => string; icon: typeof Clock; tone: string }
> = {
  created: { label: () => 'Created', icon: FileText, tone: 'text-slate-500' },
  sent: {
    label: (d) => (d?.recipientEmail ? `Sent to ${d.recipientEmail}` : 'Sent for signing'),
    icon: Send,
    tone: 'text-sky-600',
  },
  viewed: {
    label: (d) => (d?.recipientEmail ? `${d.recipientEmail} opened` : 'Opened'),
    icon: Eye,
    tone: 'text-amber-600',
  },
  signed: {
    label: (d) => (d?.recipientEmail ? `${d.recipientEmail} signed` : 'Signed'),
    icon: CheckCircle2,
    tone: 'text-emerald-600',
  },
  reminder_sent: {
    label: (d) => (d?.recipientEmail ? `Reminder → ${d.recipientEmail}` : 'Reminder sent'),
    icon: Bell,
    tone: 'text-amber-700',
  },
  completed: { label: () => 'All parties signed', icon: CheckCircle2, tone: 'text-emerald-700' },
  rejected: {
    label: (d) => (d?.recipientEmail ? `${d.recipientEmail} declined` : 'Declined'),
    icon: XCircle,
    tone: 'text-rose-600',
  },
  declined: {
    label: (d) => (d?.recipientEmail ? `${d.recipientEmail} declined` : 'Declined'),
    icon: XCircle,
    tone: 'text-rose-600',
  },
  expired: { label: () => 'Expired', icon: Clock, tone: 'text-rose-500' },
  cancelled: { label: () => 'Cancelled', icon: X, tone: 'text-slate-500' },
  deleted: { label: () => 'Deleted', icon: Trash2, tone: 'text-slate-500' },
};

function ActivityCard({ events }: { events: DetailEvent[] }) {
  const [showAll, setShowAll] = useState(false);
  // Server returns oldest-first; reverse so most recent reads top of card.
  const sorted = [...events].sort(
    (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
  );
  const visible = showAll ? sorted : sorted.slice(0, 8);

  return (
    <section className="rounded-md border bg-white p-4">
      <h2 className="mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
        Activity
      </h2>
      {sorted.length === 0 ? (
        <p className="text-xs text-muted-foreground">No events yet.</p>
      ) : (
        <>
          <ul className="space-y-2.5">
            {visible.map((e) => {
              const meta = EVENT_META[e.eventType] ?? {
                label: () => e.eventType.replace(/_/g, ' '),
                icon: Clock,
                tone: 'text-muted-foreground',
              };
              const Icon = meta.icon;
              const data = (e.eventData ?? {}) as Record<string, unknown>;
              const label = meta.label(data);
              const when = new Date(e.createdAt);
              return (
                <li key={e.id} className="flex items-start gap-2 text-xs">
                  <Icon className={`mt-0.5 size-3.5 shrink-0 ${meta.tone}`} aria-hidden />
                  <div className="min-w-0 flex-1">
                    <div className="truncate font-medium text-foreground">{label}</div>
                    <time
                      dateTime={when.toISOString()}
                      title={format(when, 'PPpp')}
                      className="text-muted-foreground"
                    >
                      {formatDistanceToNowStrict(when, { addSuffix: true })}
                    </time>
                  </div>
                </li>
              );
            })}
          </ul>
          {sorted.length > 8 ? (
            <button
              type="button"
              onClick={() => setShowAll((v) => !v)}
              className="mt-3 inline-flex items-center gap-1 text-xs text-brand hover:underline"
            >
              <ChevronDown
                className={`size-3 transition-transform ${showAll ? 'rotate-180' : ''}`}
                aria-hidden
              />
              {showAll ? 'Show fewer' : `Show all ${sorted.length} events`}
            </button>
          ) : null}
        </>
      )}
    </section>
  );
}

/**
 * #67 watcher Add UI. The watchers list previously displayed only
 * user-id stubs (truncated UUID) with a delete button and no way to
 * add new watchers. This card resolves user IDs to display names
 * via the existing `/api/v1/admin/users/picker` endpoint (already
 * used by the registry-driven settings form), surfaces a "+ Add"
 * select, and keeps the delete affordance unchanged.
 */
interface PickerUser {
  id: string;
  email: string;
  name: string | null;
}

function WatchersCard({ documentId, watchers }: { documentId: string; watchers: DetailWatcher[] }) {
  const queryClient = useQueryClient();
  const [selected, setSelected] = useState('');

  const { data: usersData } = useQuery({
    queryKey: ['admin', 'users-picker'],
    queryFn: () => apiFetch<{ data: PickerUser[] }>('/api/v1/admin/users/picker'),
  });
  const users = usersData?.data ?? [];

  const userById = useMemo(() => {
    const map = new Map<string, PickerUser>();
    for (const u of users) map.set(u.id, u);
    return map;
  }, [users]);

  const watcherIds = new Set(watchers.map((w) => w.userId));
  const candidates = users.filter((u) => !watcherIds.has(u.id));

  async function addWatcher(userId: string) {
    if (!userId) return;
    try {
      await apiFetch(`/api/v1/documents/${documentId}/watchers`, {
        method: 'POST',
        body: { userId },
      });
      toast.success('Watcher added');
      setSelected('');
      queryClient.invalidateQueries({ queryKey: ['document-detail', documentId] });
    } catch (err) {
      toastError(err);
    }
  }

  async function removeWatcher(userId: string) {
    try {
      await apiFetch(`/api/v1/documents/${documentId}/watchers/${userId}`, {
        method: 'DELETE',
      });
      toast.success('Watcher removed');
      queryClient.invalidateQueries({ queryKey: ['document-detail', documentId] });
    } catch (err) {
      toastError(err);
    }
  }

  return (
    <section className="rounded-md border bg-white p-4">
      <h2 className="mb-1 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
        Watchers
      </h2>
      <p className="mb-3 text-xs text-muted-foreground">
        Watchers receive an in-app notification on every signing event (opened, signed, declined,
        completed).
      </p>

      {watchers.length === 0 ? (
        // Larger bottom spacing (pb-1 + mb-4) gives the empty-state row enough
        // breathing room above the "Add a watcher…" select - the prior `mb-3`
        // alone left the two lines stacked tight against each other.
        <p className="mb-4 pb-1 text-xs text-muted-foreground">
          No one is watching this document yet.
        </p>
      ) : (
        <ul className="mb-3 space-y-1">
          {watchers.map((w) => {
            const u = userById.get(w.userId);
            return (
              <li key={w.userId} className="flex items-center justify-between text-sm">
                <span className="truncate">
                  {u?.name ?? u?.email ?? `User ${w.userId.slice(0, 8)}…`}
                </span>
                <button
                  type="button"
                  aria-label="Remove watcher"
                  onClick={() => removeWatcher(w.userId)}
                  className="text-muted-foreground hover:text-destructive"
                >
                  <Trash2 className="h-3.5 w-3.5" aria-hidden />
                </button>
              </li>
            );
          })}
        </ul>
      )}

      <div className="flex items-center gap-2">
        <Select value={selected} onValueChange={setSelected}>
          <SelectTrigger className="h-9 flex-1 text-xs">
            <SelectValue placeholder="Add a watcher…" />
          </SelectTrigger>
          <SelectContent>
            {candidates.length === 0 ? (
              <div className="px-2 py-3 text-xs text-muted-foreground">
                All users in this port are already watching.
              </div>
            ) : (
              candidates.map((u) => (
                <SelectItem key={u.id} value={u.id}>
                  {u.name ?? u.email}
                </SelectItem>
              ))
            )}
          </SelectContent>
        </Select>
        <Button
          size="sm"
          variant="outline"
          disabled={!selected}
          onClick={() => addWatcher(selected)}
        >
          <UserPlus className="mr-1.5 h-3 w-3" aria-hidden /> Add
        </Button>
      </div>
    </section>
  );
}
