// LinkTransfersModal — multi-select transactions and link them to a balance
// account in one shot. Issue #142 / Bilan #4.
//
// Filters available:
//   - Period (from / to ISO dates) — default: last 90 days.
//   - Category dropdown.
//   - Free-text search on description.
//
// Each row shows: date, description, amount, suggested direction
// (auto-proposed via `suggestTransferDirection` from the signed amount,
// can be flipped per row), and a checkbox.
//
// On submit, calls `linkTransfer` for every selected row in sequence and
// reports any failures (most likely `transfer_already_linked` if the user
// double-clicked or another tab linked them already).

import { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { X, Loader2, AlertCircle } from "lucide-react";
import { getTransactionPage } from "../../services/transactionService";
import {
  linkTransfer,
  suggestTransferDirection,
  BalanceServiceError,
} from "../../services/balance.service";
import type {
  Category,
  TransactionRow,
  BalanceTransferDirection,
} from "../../shared/types";

const DEFAULT_PAGE_SIZE = 100;

function isoDaysAgo(days: number): string {
  const d = new Date();
  d.setDate(d.getDate() - days);
  return localISO(d);
}

function localISO(d: Date): string {
  const yy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${yy}-${mm}-${dd}`;
}

export interface LinkTransfersModalProps {
  /** Account that the selected transfers will be attached to. */
  accountId: number;
  accountName: string;
  /** Full category list for the filter dropdown. */
  categories: Category[];
  /** Optional pre-fill date bounds (defaults to last 90 days). */
  initialFrom?: string;
  initialTo?: string;
  onClose: () => void;
  /** Fired after at least one transfer was linked (parent typically reloads). */
  onLinked?: (linkedCount: number) => void;
}

export default function LinkTransfersModal({
  accountId,
  accountName,
  categories,
  initialFrom,
  initialTo,
  onClose,
  onLinked,
}: LinkTransfersModalProps) {
  const { t, i18n } = useTranslation();

  const [from, setFrom] = useState(initialFrom ?? isoDaysAgo(90));
  const [to, setTo] = useState(initialTo ?? localISO(new Date()));
  const [categoryId, setCategoryId] = useState<number | null>(null);
  const [search, setSearch] = useState("");

  const [rows, setRows] = useState<TransactionRow[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Selection state: id → direction. Presence in the map = selected.
  const [selection, setSelection] = useState<
    Map<number, BalanceTransferDirection>
  >(new Map());
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  const fmt = useMemo(
    () =>
      new Intl.NumberFormat(i18n.language === "fr" ? "fr-CA" : "en-CA", {
        style: "currency",
        currency: "CAD",
        maximumFractionDigits: 2,
      }),
    [i18n.language]
  );

  // Re-fetch whenever the filters change. Debounced via React's render cycle
  // — typing in the search box re-runs the SQL but at < 500 rows that's fine.
  useEffect(() => {
    let cancelled = false;
    async function run() {
      setIsLoading(true);
      setError(null);
      try {
        const result = await getTransactionPage(
          {
            search: search.trim(),
            categoryId,
            sourceId: null,
            dateFrom: from || null,
            dateTo: to || null,
            uncategorizedOnly: false,
          },
          { column: "date", direction: "desc" },
          1,
          DEFAULT_PAGE_SIZE
        );
        if (!cancelled) {
          setRows(result.rows);
        }
      } catch (e) {
        if (!cancelled) {
          setError(e instanceof Error ? e.message : String(e));
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false);
        }
      }
    }
    void run();
    return () => {
      cancelled = true;
    };
  }, [from, to, categoryId, search]);

  function toggleRow(row: TransactionRow) {
    setSelection((prev) => {
      const next = new Map(prev);
      if (next.has(row.id)) {
        next.delete(row.id);
      } else {
        next.set(row.id, suggestTransferDirection(row.amount));
      }
      return next;
    });
  }

  function flipDirection(rowId: number) {
    setSelection((prev) => {
      const next = new Map(prev);
      const current = next.get(rowId);
      if (current === undefined) return prev;
      next.set(rowId, current === "in" ? "out" : "in");
      return next;
    });
  }

  async function handleSubmit() {
    if (selection.size === 0) return;
    setSubmitting(true);
    setSubmitError(null);
    let linked = 0;
    const failures: string[] = [];
    for (const [transactionId, direction] of selection.entries()) {
      try {
        await linkTransfer(accountId, transactionId, direction);
        linked += 1;
      } catch (e) {
        if (e instanceof BalanceServiceError) {
          failures.push(`${transactionId}: ${t(`balance.transfers.errors.${e.code}`, { defaultValue: e.message })}`);
        } else {
          failures.push(`${transactionId}: ${e instanceof Error ? e.message : String(e)}`);
        }
      }
    }
    setSubmitting(false);
    if (failures.length > 0) {
      setSubmitError(
        `${t("balance.transfers.modal.partialFailure", { linked, total: selection.size })} — ${failures.join("; ")}`
      );
    }
    if (linked > 0) {
      onLinked?.(linked);
      if (failures.length === 0) {
        onClose();
      }
    }
  }

  const allFiltered = rows.length;
  const selectedCount = selection.size;

  return createPortal(
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={onClose}
    >
      <div
        className="bg-[var(--card)] border border-[var(--border)] rounded-xl shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 py-4 border-b border-[var(--border)]">
          <div>
            <h2 className="text-lg font-semibold">
              {t("balance.transfers.modal.title", { account: accountName })}
            </h2>
            <p className="text-xs text-[var(--muted-foreground)] mt-0.5">
              {t("balance.transfers.modal.subtitle")}
            </p>
          </div>
          <button
            type="button"
            onClick={onClose}
            className="p-1 rounded hover:bg-[var(--muted)]/40"
            aria-label={t("common.close")}
          >
            <X size={18} />
          </button>
        </div>

        <div className="px-5 py-3 border-b border-[var(--border)] grid grid-cols-1 md:grid-cols-4 gap-3">
          <label className="text-xs">
            <span className="block text-[var(--muted-foreground)] mb-1">
              {t("balance.transfers.modal.from")}
            </span>
            <input
              type="date"
              value={from}
              onChange={(e) => {
                setFrom(e.target.value);
                // Close native date popup on WebKitGTK (#177)
                e.currentTarget.blur();
              }}
              className="w-full px-2 py-1.5 bg-[var(--background)] border border-[var(--border)] rounded text-sm"
            />
          </label>
          <label className="text-xs">
            <span className="block text-[var(--muted-foreground)] mb-1">
              {t("balance.transfers.modal.to")}
            </span>
            <input
              type="date"
              value={to}
              onChange={(e) => {
                setTo(e.target.value);
                // Close native date popup on WebKitGTK (#177)
                e.currentTarget.blur();
              }}
              className="w-full px-2 py-1.5 bg-[var(--background)] border border-[var(--border)] rounded text-sm"
            />
          </label>
          <label className="text-xs">
            <span className="block text-[var(--muted-foreground)] mb-1">
              {t("balance.transfers.modal.category")}
            </span>
            <select
              value={categoryId === null ? "" : String(categoryId)}
              onChange={(e) =>
                setCategoryId(e.target.value === "" ? null : Number(e.target.value))
              }
              className="w-full px-2 py-1.5 bg-[var(--background)] border border-[var(--border)] rounded text-sm"
            >
              <option value="">{t("balance.transfers.modal.anyCategory")}</option>
              {categories.map((c) => (
                <option key={c.id} value={c.id}>
                  {c.name}
                </option>
              ))}
            </select>
          </label>
          <label className="text-xs">
            <span className="block text-[var(--muted-foreground)] mb-1">
              {t("balance.transfers.modal.search")}
            </span>
            <input
              type="text"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              placeholder={t("balance.transfers.modal.searchPlaceholder")}
              className="w-full px-2 py-1.5 bg-[var(--background)] border border-[var(--border)] rounded text-sm"
            />
          </label>
        </div>

        <div className="flex-1 overflow-auto">
          {isLoading ? (
            <div className="p-8 text-center text-[var(--muted-foreground)] flex items-center justify-center gap-2">
              <Loader2 className="animate-spin" size={16} />
              {t("balance.transfers.modal.loading")}
            </div>
          ) : error ? (
            <div className="p-8 text-center text-[var(--negative)] flex items-center justify-center gap-2">
              <AlertCircle size={16} />
              {error}
            </div>
          ) : rows.length === 0 ? (
            <div className="p-8 text-center text-[var(--muted-foreground)] italic">
              {t("balance.transfers.modal.noTransactions")}
            </div>
          ) : (
            <table className="w-full text-sm">
              <thead className="bg-[var(--muted)]/30 sticky top-0">
                <tr>
                  <th className="w-10 px-3 py-2"></th>
                  <th className="text-left px-3 py-2 font-medium">
                    {t("transactions.date")}
                  </th>
                  <th className="text-left px-3 py-2 font-medium">
                    {t("transactions.description")}
                  </th>
                  <th className="text-right px-3 py-2 font-medium">
                    {t("transactions.amount")}
                  </th>
                  <th className="text-center px-3 py-2 font-medium">
                    {t("balance.transfers.modal.direction")}
                  </th>
                </tr>
              </thead>
              <tbody>
                {rows.map((row) => {
                  const isSelected = selection.has(row.id);
                  const direction = selection.get(row.id) ?? suggestTransferDirection(row.amount);
                  return (
                    <tr
                      key={row.id}
                      className="border-t border-[var(--border)] hover:bg-[var(--muted)]/10"
                    >
                      <td className="px-3 py-2 text-center">
                        <input
                          type="checkbox"
                          checked={isSelected}
                          onChange={() => toggleRow(row)}
                          aria-label={`select-${row.id}`}
                        />
                      </td>
                      <td className="px-3 py-2 whitespace-nowrap">{row.date}</td>
                      <td className="px-3 py-2 max-w-md truncate" title={row.description}>
                        {row.description}
                      </td>
                      <td
                        className={`px-3 py-2 text-right font-mono ${row.amount >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}
                      >
                        {fmt.format(row.amount)}
                      </td>
                      <td className="px-3 py-2 text-center">
                        {isSelected ? (
                          <button
                            type="button"
                            onClick={() => flipDirection(row.id)}
                            className={`px-2 py-0.5 text-xs rounded font-medium ${
                              direction === "in"
                                ? "bg-[var(--positive)]/15 text-[var(--positive)]"
                                : "bg-[var(--negative)]/15 text-[var(--negative)]"
                            }`}
                            title={t("balance.transfers.modal.toggleDirection")}
                          >
                            {t(`balance.transfers.direction.${direction}`)}
                          </button>
                        ) : (
                          <span className="text-xs text-[var(--muted-foreground)]">
                            {t(`balance.transfers.direction.${direction}`)}
                          </span>
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>

        {submitError && (
          <div className="px-5 py-2 border-t border-[var(--border)] text-xs text-[var(--negative)]">
            {submitError}
          </div>
        )}

        <div className="px-5 py-3 border-t border-[var(--border)] flex items-center justify-between">
          <div className="text-xs text-[var(--muted-foreground)]">
            {t("balance.transfers.modal.summary", {
              selected: selectedCount,
              total: allFiltered,
            })}
          </div>
          <div className="flex gap-2">
            <button
              type="button"
              onClick={onClose}
              className="px-3 py-1.5 text-sm rounded border border-[var(--border)] hover:bg-[var(--muted)]/30"
            >
              {t("common.cancel")}
            </button>
            <button
              type="button"
              onClick={handleSubmit}
              disabled={submitting || selectedCount === 0}
              className="px-3 py-1.5 text-sm rounded bg-[var(--primary)] text-white disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {submitting ? (
                <span className="flex items-center gap-1.5">
                  <Loader2 className="animate-spin" size={14} />
                  {t("balance.transfers.modal.linking")}
                </span>
              ) : (
                t("balance.transfers.modal.linkSelection", { count: selectedCount })
              )}
            </button>
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}
