// app/admin/transacciones/page.tsx
// Transaction table (admin.md section 2.3). Filters by status, rail, date range,
// and free-text (name / phone / email / provider charge id). PII masked. A
// refund control (operator+) sits inline per row and calls the refund route.

import * as React from "react";
import { redirect } from "next/navigation";
import { requireRole, getSession, roleSatisfies, AuthError } from "@/lib/auth";
import { listRaffles, getTransactions } from "@/app/admin/_lib/queries";
import { maskPhone, maskEmail } from "@/app/admin/_lib/format";
import { RaffleSwitcher } from "@/components/admin/RaffleSwitcher";
import { TransactionTable } from "@/components/admin/TransactionTable";
import { AdminPagination } from "@/components/admin/AdminPagination";
import { Card } from "@/components/ui";
import type { PurchaseStatus, PaymentMethod } from "@/lib/types";

export const dynamic = "force-dynamic";

const STATUSES: PurchaseStatus[] = [
  "pending",
  "paid",
  "failed",
  "expired",
  "refunded",
];
const METHODS: PaymentMethod[] = ["yape", "card", "pago_efectivo", "other"];

export default async function TransactionsPage({
  searchParams,
}: {
  searchParams: Promise<{
    raffle?: string;
    status?: string;
    method?: string;
    q?: string;
    offset?: string;
  }>;
}) {
  try {
    await requireRole("viewer");
  } catch (err) {
    if (err instanceof AuthError) redirect("/admin/login");
    throw err;
  }
  const session = await getSession();
  const canRefund = session ? roleSatisfies(session.role, "operator") : false;
  const canUnmask = canRefund;

  const raffles = await listRaffles();
  if (raffles.length === 0) {
    return (
      <div>
        <h1 className="font-impact text-heading-lg font-bold text-ink-strong">
          Transacciones
        </h1>
        <p className="mt-4 font-body text-body-lg text-muted">
          Aún no hay sorteos creados.
        </p>
      </div>
    );
  }

  const sp = await searchParams;
  const selected = raffles.find((r) => r.id === sp.raffle) ?? raffles[0];
  const status = STATUSES.includes(sp.status as PurchaseStatus)
    ? (sp.status as PurchaseStatus)
    : undefined;
  const method = METHODS.includes(sp.method as PaymentMethod)
    ? (sp.method as PaymentMethod)
    : undefined;

  const pageSize = 50;
  const offset = Math.max(parseInt(sp.offset ?? "0", 10) || 0, 0);
  // Fetch one extra row to learn whether a next page exists (no count query).
  const fetched = await getTransactions(selected.id, {
    status,
    method,
    search: sp.q,
    limit: pageSize + 1,
    offset,
  });
  const hasNext = fetched.length > pageSize;
  const rows = hasNext ? fetched.slice(0, pageSize) : fetched;

  const tableRows = rows.map((r) => ({
    id: r.id,
    createdAt: r.createdAt.toISOString(),
    paidAt: r.paidAt ? r.paidAt.toISOString() : null,
    participantName: r.participantName,
    // Always render masked by default. The raw contact is sent to operator+ only,
    // and the table reveals it ONLY after a logged unmask action fires (mirrors
    // the participant table). No unconditional full-PII payload.
    phoneMasked: maskPhone(r.phoneE164),
    emailMasked: maskEmail(r.emailNormalized),
    phoneRaw: canUnmask ? r.phoneE164 : null,
    emailRaw: canUnmask ? r.emailNormalized : null,
    status: r.status,
    chances: r.chances,
    amountCents: r.amountCents,
    feeCoverCents: r.feeCoverCents,
    method: r.method,
    providerName: r.providerName,
    providerChargeId: r.providerChargeId,
    providerStatus: r.providerStatus,
    channel: r.influencerCode ?? r.utmSource ?? null,
  }));

  return (
    <div className="flex flex-col gap-lg">
      <div className="flex flex-wrap items-center justify-between gap-md">
        <h1 className="font-impact text-heading-lg font-bold text-ink-strong">
          Transacciones
        </h1>
        <RaffleSwitcher
          raffles={raffles.map((r) => ({ id: r.id, name: r.name, status: r.status }))}
          selectedId={selected.id}
        />
      </div>

      <form method="get" className="flex flex-wrap items-end gap-3">
        <input type="hidden" name="raffle" value={selected.id} />
        <Field label="Estado" htmlFor="status">
          <select
            id="status"
            name="status"
            defaultValue={status ?? ""}
            className="min-h-11 rounded-md border border-track bg-canvas px-3 font-body text-body-md text-ink-strong focus-visible:outline focus-visible:outline-[3px] focus-visible:outline-offset-1 focus-visible:outline-steelblue"
          >
            <option value="">Todos</option>
            {STATUSES.map((s) => (
              <option key={s} value={s}>
                {s}
              </option>
            ))}
          </select>
        </Field>
        <Field label="Medio" htmlFor="method">
          <select
            id="method"
            name="method"
            defaultValue={method ?? ""}
            className="min-h-11 rounded-md border border-track bg-canvas px-3 font-body text-body-md text-ink-strong focus-visible:outline focus-visible:outline-[3px] focus-visible:outline-offset-1 focus-visible:outline-steelblue"
          >
            <option value="">Todos</option>
            {METHODS.map((m) => (
              <option key={m} value={m}>
                {m}
              </option>
            ))}
          </select>
        </Field>
        <Field label="Buscar" htmlFor="q">
          <input
            id="q"
            name="q"
            type="search"
            defaultValue={sp.q ?? ""}
            placeholder="nombre, contacto o cargo"
            className="min-h-11 w-64 rounded-md border border-track bg-canvas px-4 font-body text-body-md text-ink-strong focus-visible:outline focus-visible:outline-[3px] focus-visible:outline-offset-1 focus-visible:outline-steelblue"
          />
        </Field>
        <button
          type="submit"
          className="min-h-11 rounded-md border border-primary px-5 font-body text-body-md font-bold text-primary hover:bg-primary hover:text-on-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
        >
          Filtrar
        </button>
      </form>

      <div>
        <a
          href={`/api/admin/raffles/${selected.id}/transactions.csv`}
          className="inline-flex min-h-11 items-center rounded-md border border-primary px-4 font-body text-body-md font-medium text-primary hover:bg-primary hover:text-on-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
        >
          Descargar CSV de transacciones
        </a>
      </div>

      <Card elevation="tight" className="p-lg">
        <TransactionTable rows={tableRows} canRefund={canRefund} canUnmask={canUnmask} />
      </Card>

      <AdminPagination
        basePath="/admin/transacciones"
        params={{
          raffle: selected.id,
          ...(status ? { status } : {}),
          ...(method ? { method } : {}),
          ...(sp.q ? { q: sp.q } : {}),
        }}
        offset={offset}
        pageSize={pageSize}
        shown={rows.length}
        hasNext={hasNext}
      />
    </div>
  );
}

function Field({
  label,
  htmlFor,
  children,
}: {
  label: string;
  htmlFor: string;
  children: React.ReactNode;
}) {
  return (
    <div className="flex flex-col gap-1.5">
      <label
        htmlFor={htmlFor}
        className="font-body text-body-md font-medium text-ink-strong"
      >
        {label}
      </label>
      {children}
    </div>
  );
}
