// app/admin/participantes/page.tsx
// Participant table (admin.md section 2.2). PII masked by default. Unmasking is a
// LOGGED action (writeAudit actor=admin:<id>) available only to operator+ roles.
// Search by name / phone / email.
//
// PII-on-the-wire policy: raw phone/email are sent to the client ONLY when the
// session role is operator+ (who may already unmask). The actual reveal click
// then writes the audit row. Viewers receive masked values only.

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

export const dynamic = "force-dynamic";

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

  const raffles = await listRaffles();
  if (raffles.length === 0) {
    return <EmptyState />;
  }

  const { raffle: raffleParam, q, paid, offset: offsetParam } = await searchParams;
  const selected = raffles.find((r) => r.id === raffleParam) ?? raffles[0];
  const paidOnly = paid === "1";
  const pageSize = 50;
  const offset = Math.max(parseInt(offsetParam ?? "0", 10) || 0, 0);
  // Fetch one extra row to learn whether a next page exists (no count query).
  const fetched = await getParticipants(selected.id, {
    search: q,
    limit: pageSize + 1,
    offset,
    paidOnly,
  });
  const hasNext = fetched.length > pageSize;
  const rows = hasNext ? fetched.slice(0, pageSize) : fetched;

  const tableRows = rows.map((r) => ({
    id: r.id,
    fullName: r.fullName,
    phoneMasked: maskPhone(r.phoneE164),
    emailMasked: maskEmail(r.emailNormalized),
    // Raw only for operator+; viewers get null.
    phoneRaw: canUnmask ? r.phoneE164 : null,
    emailRaw: canUnmask ? r.emailNormalized : null,
    marketingConsent: r.marketingConsent,
    chancesThisRaffle: r.chancesThisRaffle,
    paidPurchases: r.paidPurchases,
    totalPaidCents: r.totalPaidCents,
    createdAt: r.createdAt.toISOString(),
  }));

  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">
          Participantes
        </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} />
        <div className="flex flex-col gap-1.5">
          <label
            htmlFor="q"
            className="font-body text-body-md font-medium text-ink-strong"
          >
            Buscar (nombre, teléfono o correo)
          </label>
          <input
            id="q"
            name="q"
            type="search"
            defaultValue={q ?? ""}
            className="min-h-11 w-72 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"
          />
        </div>
        <label className="flex min-h-11 items-center gap-2 font-body text-body-md text-ink-strong">
          <input
            type="checkbox"
            name="paid"
            value="1"
            defaultChecked={paidOnly}
            className="h-4 w-4 accent-primary"
          />
          Solo pagados
        </label>
        <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"
        >
          Buscar
        </button>
      </form>

      <p className="font-body text-caption text-muted">
        El panel cuenta participantes con al menos un pago confirmado. Esta tabla los
        incluye a todos por defecto (incluidos quienes solo tienen compras sin pagar);
        marca &quot;Solo pagados&quot; para ver exactamente ese grupo. Haz clic en un
        nombre para ver sus números de boleto y cuánto pagó.
      </p>

      <div>
        <a
          href={`/api/admin/raffles/${selected.id}/participants.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 participantes
        </a>
      </div>

      <Card elevation="tight" className="p-lg">
        <ParticipantTable
          rows={tableRows}
          canUnmask={canUnmask}
          raffleId={selected.id}
        />
        {!canUnmask ? (
          <p className="mt-4 font-body text-caption text-muted">
            Los datos de contacto se muestran enmascarados. Solo un operador o
            administrador puede revelarlos, y cada revelación queda registrada.
          </p>
        ) : null}
      </Card>

      <AdminPagination
        basePath="/admin/participantes"
        params={{
          raffle: selected.id,
          ...(q ? { q } : {}),
          ...(paidOnly ? { paid: "1" } : {}),
        }}
        offset={offset}
        pageSize={pageSize}
        shown={rows.length}
        hasNext={hasNext}
      />
    </div>
  );
}

function EmptyState() {
  return (
    <div>
      <h1 className="font-impact text-heading-lg font-bold text-ink-strong">
        Participantes
      </h1>
      <p className="mt-4 font-body text-body-lg text-muted">
        Aún no hay sorteos creados.
      </p>
    </div>
  );
}
