"use client";

import { useState, useMemo, useCallback } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
  useReactTable,
  getCoreRowModel,
  createColumnHelper,
  flexRender,
  type RowData,
} from "@tanstack/react-table";
import { MEASURE_NAMES } from "@/app/lib/measureDocumentRequirements";
import {
  toDateInputValue,
  dateInputToIso,
  formatDate,
} from "@/app/lib/pibiSectionHelpers";
import type { PibiRow } from "@/app/lib/pibiSectionHelpers";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/app/shadcn_components/ui/tooltip";

// ── TableMeta augmentation ────────────────────────────────────────────────────

declare module "@tanstack/react-table" {
  interface TableMeta<TData extends RowData> {
    pibi?: PibiTableMeta;
  }
}

// ── Types ─────────────────────────────────────────────────────────────────────

interface EditableRow {
  id: string;
  isNew: boolean;
  measureName: string;
  orderedAt: string;   // yyyy-mm-dd or ""
  completedAt: string; // yyyy-mm-dd or ""
  isDirty: boolean;
  isSaving: boolean;
  error: string | null;
}

interface NewRowData {
  id: string;
  measureName: string;
  orderedAt: string;
  completedAt: string;
}

interface PibiTableMeta {
  canEdit: boolean;
  approvedMeasures: string[];
  proposedMeasures: string[];
  updateField(
    id: string,
    field: "measureName" | "orderedAt" | "completedAt",
    value: string,
  ): void;
  saveRow(id: string): void;
  deleteRow(id: string): void;
}

// ── New row ID counter ────────────────────────────────────────────────────────

let newRowSeq = 0;
function nextNewId() {
  return `new-${++newRowSeq}`;
}

// ── Scope badge ───────────────────────────────────────────────────────────────

function ScopeBadge({
  measure,
  approved,
  proposed,
}: {
  measure: string;
  approved: string[];
  proposed: string[];
}) {
  if (approved.includes(measure)) {
    return (
      <Tooltip>
        <TooltipTrigger asChild>
          <span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 border border-emerald-200 text-emerald-700 cursor-default">
            Approved
          </span>
        </TooltipTrigger>
        <TooltipContent>This measure has been technically approved for installation</TooltipContent>
      </Tooltip>
    );
  }
  if (proposed.includes(measure)) {
    return (
      <Tooltip>
        <TooltipTrigger asChild>
          <span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-semibold bg-blue-50 border border-blue-200 text-blue-700 cursor-default">
            Proposed
          </span>
        </TooltipTrigger>
        <TooltipContent>This measure has been proposed but not yet approved</TooltipContent>
      </Tooltip>
    );
  }
  return null;
}

// ── Column definitions ────────────────────────────────────────────────────────

const columnHelper = createColumnHelper<EditableRow>();

const COLUMNS = [
  columnHelper.accessor("measureName", {
    header: "Measure",
    cell: ({ row, table }) => {
      const meta = table.options.meta!.pibi!;
      const r = row.original;
      return (
        <div className="flex items-center gap-1.5">
          {meta.canEdit ? (
            <select
              value={r.measureName}
              onChange={(e) =>
                meta.updateField(r.id, "measureName", e.target.value)
              }
              disabled={r.isSaving}
              data-testid={`pibi-measure-select-${r.id}`}
              className="rounded border border-gray-200 px-1.5 py-0.5 text-xs text-gray-800 focus:outline-none focus:ring-1 focus:ring-brandblue/40 w-[130px]"
            >
              {(() => {
                const approved = MEASURE_NAMES.filter((m) =>
                  meta.approvedMeasures.includes(m),
                );
                const proposed = MEASURE_NAMES.filter(
                  (m) =>
                    meta.proposedMeasures.includes(m) &&
                    !meta.approvedMeasures.includes(m),
                );
                const other = MEASURE_NAMES.filter(
                  (m) =>
                    !meta.approvedMeasures.includes(m) &&
                    !meta.proposedMeasures.includes(m),
                );
                return (
                  <>
                    {approved.length > 0 && (
                      <optgroup label="Approved">
                        {approved.map((m) => (
                          <option key={m} value={m}>
                            {m}
                          </option>
                        ))}
                      </optgroup>
                    )}
                    {proposed.length > 0 && (
                      <optgroup label="Proposed">
                        {proposed.map((m) => (
                          <option key={m} value={m}>
                            {m}
                          </option>
                        ))}
                      </optgroup>
                    )}
                    {other.length > 0 && (
                      <optgroup label="Other">
                        {other.map((m) => (
                          <option key={m} value={m}>
                            {m}
                          </option>
                        ))}
                      </optgroup>
                    )}
                  </>
                );
              })()}
            </select>
          ) : (
            <span className="text-xs text-gray-800">{r.measureName}</span>
          )}
          <ScopeBadge
            measure={r.measureName}
            approved={meta.approvedMeasures}
            proposed={meta.proposedMeasures}
          />
        </div>
      );
    },
  }),

  columnHelper.accessor("orderedAt", {
    header: "Ordered",
    cell: ({ row, table }) => {
      const meta = table.options.meta!.pibi!;
      const r = row.original;
      if (!meta.canEdit) {
        return (
          <span className="text-xs text-gray-500">
            {formatDate(r.orderedAt)}
          </span>
        );
      }
      return (
        <input
          type="date"
          value={r.orderedAt}
          onChange={(e) =>
            meta.updateField(r.id, "orderedAt", e.target.value)
          }
          disabled={r.isSaving}
          data-testid={`pibi-ordered-date-${r.id}`}
          className="rounded border border-gray-200 px-1.5 py-0.5 text-xs text-gray-800 focus:outline-none focus:ring-1 focus:ring-brandblue/40 w-[110px]"
        />
      );
    },
  }),

  columnHelper.accessor("completedAt", {
    header: "Completed",
    cell: ({ row, table }) => {
      const meta = table.options.meta!.pibi!;
      const r = row.original;
      if (!meta.canEdit) {
        return r.completedAt ? (
          <span className="text-xs text-emerald-600 font-medium">
            {formatDate(r.completedAt)}
          </span>
        ) : (
          <span className="text-xs text-gray-300">—</span>
        );
      }
      return (
        <input
          type="date"
          value={r.completedAt}
          onChange={(e) =>
            meta.updateField(r.id, "completedAt", e.target.value)
          }
          disabled={r.isSaving}
          data-testid={`pibi-completed-date-${r.id}`}
          className="rounded border border-gray-200 px-1.5 py-0.5 text-xs text-gray-800 focus:outline-none focus:ring-1 focus:ring-brandblue/40 w-[110px]"
        />
      );
    },
  }),

  columnHelper.display({
    id: "actions",
    header: "",
    cell: ({ row, table }) => {
      const meta = table.options.meta!.pibi!;
      if (!meta.canEdit) return null;
      const r = row.original;
      return (
        <div className="flex items-center gap-1.5">
          <button
            onClick={() => meta.saveRow(r.id)}
            disabled={!r.isDirty || r.isSaving}
            data-testid={`pibi-save-${r.id}`}
            className="text-[10px] font-medium px-2.5 py-1 rounded-lg bg-brandblue text-white hover:bg-brandmidblue disabled:opacity-30 transition-colors"
          >
            {r.isSaving ? "Saving…" : "Save"}
          </button>
          <button
            onClick={() => meta.deleteRow(r.id)}
            disabled={r.isSaving}
            data-testid={`pibi-delete-${r.id}`}
            className="text-[10px] font-medium px-2.5 py-1 rounded-lg border border-red-100 text-red-400 hover:bg-red-50 disabled:opacity-30 transition-colors"
          >
            Delete
          </button>
          {r.error && (
            <span className="text-[10px] text-red-600">{r.error}</span>
          )}
        </div>
      );
    },
  }),
];

// ── Main component ────────────────────────────────────────────────────────────

export interface PibiSectionProps {
  dealId: string;
  portfolioId: string;
  proposedMeasures: string[];
  canEdit: boolean;
}

export function PibiSection({
  dealId,
  portfolioId,
  proposedMeasures,
  canEdit,
}: PibiSectionProps) {
  const queryClient = useQueryClient();

  // Local edit state for existing rows
  const [localEdits, setLocalEdits] = useState<
    Record<
      string,
      Partial<{ measureName: string; orderedAt: string; completedAt: string }>
    >
  >({});

  // Unsaved new rows
  const [newRows, setNewRows] = useState<NewRowData[]>([]);

  // Row IDs currently being saved or deleted
  const [savingIds, setSavingIds] = useState<Set<string>>(new Set());

  // Per-row error messages
  const [rowErrors, setRowErrors] = useState<Record<string, string>>({});

  // ── Server data ───────────────────────────────────────────────────────────

  const { data, isLoading } = useQuery<{ pibiRequests: PibiRow[] }>({
    queryKey: ["pibiRequests", portfolioId, dealId],
    queryFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/pibi-requests?dealId=${encodeURIComponent(dealId)}`,
      );
      if (!res.ok) throw new Error("Failed to fetch PIBI requests");
      return res.json();
    },
    staleTime: 30_000,
  });

  const { data: measureData } = useQuery<{
    approvedMeasures: string[];
    instructedMeasures: string[];
  }>({
    queryKey: ["pibiMeasures", portfolioId, dealId],
    queryFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/pibi-measures?dealId=${encodeURIComponent(dealId)}`,
      );
      if (!res.ok) throw new Error("Failed to fetch measures");
      return res.json();
    },
    staleTime: 60_000,
  });

  const approvedMeasures = useMemo(
    () => [
      ...(measureData?.approvedMeasures ?? []),
      ...(measureData?.instructedMeasures ?? []),
    ],
    [measureData],
  );

  // ── Derived table rows ────────────────────────────────────────────────────

  const tableRows: EditableRow[] = useMemo(() => {
    const serverRows = (data?.pibiRequests ?? []).map(
      (row): EditableRow => ({
        id: row.id,
        isNew: false,
        measureName: localEdits[row.id]?.measureName ?? row.measureName,
        orderedAt:
          localEdits[row.id]?.orderedAt ?? toDateInputValue(row.orderedAt),
        completedAt:
          localEdits[row.id]?.completedAt ??
          toDateInputValue(row.completedAt),
        isDirty: !!localEdits[row.id],
        isSaving: savingIds.has(row.id),
        error: rowErrors[row.id] ?? null,
      }),
    );

    const pendingRows = newRows.map(
      (row): EditableRow => ({
        id: row.id,
        isNew: true,
        measureName: row.measureName,
        orderedAt: row.orderedAt,
        completedAt: row.completedAt,
        isDirty: true,
        isSaving: savingIds.has(row.id),
        error: rowErrors[row.id] ?? null,
      }),
    );

    return [...serverRows, ...pendingRows];
  }, [data, localEdits, newRows, savingIds, rowErrors]);

  // ── Handlers ──────────────────────────────────────────────────────────────

  const updateField = useCallback(
    (
      id: string,
      field: "measureName" | "orderedAt" | "completedAt",
      value: string,
    ) => {
      if (id.startsWith("new-")) {
        setNewRows((prev) =>
          prev.map((r) => (r.id === id ? { ...r, [field]: value } : r)),
        );
      } else {
        setLocalEdits((prev) => ({
          ...prev,
          [id]: { ...prev[id], [field]: value },
        }));
      }
    },
    [],
  );

  const saveRow = useCallback(
    async (id: string) => {
      const row = tableRows.find((r) => r.id === id);
      if (!row) return;

      setSavingIds((prev) => new Set(prev).add(id));
      setRowErrors((prev) => {
        const n = { ...prev };
        delete n[id];
        return n;
      });

      try {
        let res: Response;

        if (row.isNew) {
          res = await fetch(
            `/api/portfolio/${portfolioId}/pibi-requests`,
            {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                dealId,
                measureNames: [row.measureName],
                orderedAt: row.orderedAt
                  ? new Date(`${row.orderedAt}T00:00:00.000Z`).toISOString()
                  : undefined,
              }),
            },
          );
        } else {
          res = await fetch(
            `/api/portfolio/${portfolioId}/pibi-requests/${id}`,
            {
              method: "PATCH",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                dealId,
                measureName: row.measureName,
                ...(row.orderedAt && {
                  orderedAt: dateInputToIso(row.orderedAt),
                }),
                completedAt: row.completedAt
                  ? dateInputToIso(row.completedAt)
                  : null,
              }),
            },
          );
        }

        if (!res.ok) {
          const json = await res.json().catch(() => ({}));
          throw new Error(
            typeof json.error === "string" ? json.error : "Save failed",
          );
        }

        if (row.isNew) {
          setNewRows((prev) => prev.filter((r) => r.id !== id));
        } else {
          setLocalEdits((prev) => {
            const n = { ...prev };
            delete n[id];
            return n;
          });
        }

        await queryClient.invalidateQueries({
          queryKey: ["pibiRequests", portfolioId, dealId],
        });
      } catch (err) {
        setRowErrors((prev) => ({
          ...prev,
          [id]: err instanceof Error ? err.message : "Save failed",
        }));
      } finally {
        setSavingIds((prev) => {
          const n = new Set(prev);
          n.delete(id);
          return n;
        });
      }
    },
    [tableRows, dealId, portfolioId, queryClient],
  );

  const deleteRow = useCallback(
    async (id: string) => {
      const row = tableRows.find((r) => r.id === id);
      if (!row) return;

      if (row.isNew) {
        setNewRows((prev) => prev.filter((r) => r.id !== id));
        return;
      }

      setSavingIds((prev) => new Set(prev).add(id));
      setRowErrors((prev) => {
        const n = { ...prev };
        delete n[id];
        return n;
      });

      try {
        const res = await fetch(
          `/api/portfolio/${portfolioId}/pibi-requests/${id}?dealId=${encodeURIComponent(dealId)}`,
          { method: "DELETE" },
        );
        if (!res.ok) throw new Error("Delete failed");

        await queryClient.invalidateQueries({
          queryKey: ["pibiRequests", portfolioId, dealId],
        });
      } catch (err) {
        setRowErrors((prev) => ({
          ...prev,
          [id]: err instanceof Error ? err.message : "Delete failed",
        }));
      } finally {
        setSavingIds((prev) => {
          const n = new Set(prev);
          n.delete(id);
          return n;
        });
      }
    },
    [tableRows, dealId, portfolioId, queryClient],
  );

  function addRow() {
    const today = new Date().toISOString().slice(0, 10);
    setNewRows((prev) => [
      ...prev,
      {
        id: nextNewId(),
        measureName: MEASURE_NAMES[0],
        orderedAt: today,
        completedAt: "",
      },
    ]);
  }

  async function markAllComplete() {
    const now = new Date().toISOString();
    const incomplete = tableRows.filter((r) => !r.isNew && !r.completedAt);
    await Promise.all(
      incomplete.map((r) =>
        fetch(`/api/portfolio/${portfolioId}/pibi-requests/${r.id}`, {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ dealId, completedAt: now }),
        }),
      ),
    );
    await queryClient.invalidateQueries({
      queryKey: ["pibiRequests", portfolioId, dealId],
    });
  }

  // ── Table instance ────────────────────────────────────────────────────────

  const table = useReactTable({
    data: tableRows,
    columns: COLUMNS,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      pibi: {
        canEdit,
        approvedMeasures,
        proposedMeasures,
        updateField,
        saveRow,
        deleteRow,
      },
    },
  });

  // ── Render ────────────────────────────────────────────────────────────────

  if (isLoading) {
    return (
      <div className="flex items-center justify-center py-6">
        <span className="text-xs text-gray-400">Loading PIBIs…</span>
      </div>
    );
  }

  const hasIncomplete = tableRows.some((r) => !r.isNew && !r.completedAt);

  return (
    <TooltipProvider>
    <div className="space-y-3" data-testid="pibi-section">
      {/* Header actions */}
      <div className="flex items-center gap-2">
        {canEdit && hasIncomplete && (
          <button
            onClick={markAllComplete}
            data-testid="pibi-mark-all-complete"
            className="text-xs font-medium px-3 py-1.5 rounded-lg bg-emerald-600 text-white hover:bg-emerald-700 transition-colors"
          >
            Mark all complete
          </button>
        )}
        {canEdit && (
          <button
            onClick={addRow}
            data-testid="pibi-add-row"
            className="ml-auto text-xs font-medium px-3 py-1.5 rounded-lg bg-brandblue text-white hover:bg-brandmidblue transition-colors"
          >
            + Add row
          </button>
        )}
      </div>

      {/* Empty state */}
      {tableRows.length === 0 && (
        <div
          data-testid="pibi-empty-state"
          className="flex flex-col items-center justify-center py-8 rounded-xl border border-dashed border-gray-200 bg-gray-50/50 space-y-2"
        >
          <p className="text-xs text-gray-400 font-medium">
            No PIBIs logged yet
          </p>
          {canEdit && (
            <button
              onClick={addRow}
              data-testid="pibi-empty-add-row"
              className="text-xs font-medium px-3 py-1.5 rounded-lg border border-brandblue/20 text-brandblue hover:bg-brandlightblue/20 transition-colors"
            >
              Log first PIBI
            </button>
          )}
        </div>
      )}

      {/* Table */}
      {tableRows.length > 0 && (
        <div className="rounded-xl border border-gray-100 overflow-x-auto">
          <table className="w-full text-xs min-w-[560px]">
            <thead>
              {table.getHeaderGroups().map((hg) => (
                <tr
                  key={hg.id}
                  className="border-b border-gray-100 bg-gray-50"
                >
                  {hg.headers.map((h) => (
                    <th
                      key={h.id}
                      className="px-2 py-1.5 text-left text-[10px] font-semibold text-gray-400 uppercase tracking-wider whitespace-nowrap"
                    >
                      {flexRender(
                        h.column.columnDef.header,
                        h.getContext(),
                      )}
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody className="divide-y divide-gray-50">
              {table.getRowModel().rows.map((row) => (
                <tr
                  key={row.id}
                  data-testid={`pibi-row-${row.original.id}`}
                  className={
                    row.original.completedAt
                      ? "bg-emerald-50/30"
                      : "bg-white"
                  }
                >
                  {row.getVisibleCells().map((cell) => (
                    <td key={cell.id} className="px-2 py-1.5">
                      {flexRender(
                        cell.column.columnDef.cell,
                        cell.getContext(),
                      )}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
    </TooltipProvider>
  );
}
