"use client";

/**
 * Export approval queue.
 *
 * A super admin sees every request and can approve or reject; anyone else sees
 * only their own, read-only. That split is decided by core (`canReview` in the
 * response), not here — the UI follows the server's answer rather than deciding
 * for itself from the JWT, so hiding the buttons can't drift from what the API
 * actually permits.
 *
 * The workbook is downloadable from the File column. That is how an approver sees
 * what they are approving: the notification mail deliberately doesn't attach it, so
 * the data isn't released to inboxes ahead of the approval that gates it.
 */

import {
  Badge,
  Button,
  Card,
  Container,
  Group,
  Modal,
  SegmentedControl,
  Skeleton,
  Stack,
  Table,
  Text,
  Textarea,
  Title,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import {
  IconCheck,
  IconDownload,
  IconMailForward,
  IconX,
} from "@tabler/icons-react";
import { useCallback, useEffect, useState } from "react";
import {
  exportRequestFileUrl,
  listExportRequests,
  reviewExportRequest,
  type ExportRequest,
  type ExportRequestStatus,
} from "@/lib/features/export-requests/query";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";

const PAGE_SIZE = 25;

const STATUS_COLOR: Record<ExportRequestStatus, string> = {
  pending: "orange",
  approved: "green",
  rejected: "red",
};

function formatWhen(value: string | null): string {
  if (!value) return "—";
  const d = new Date(value);
  return Number.isFinite(d.getTime()) ? d.toLocaleString() : "—";
}

function formatSize(bytes: number): string {
  if (!bytes) return "—";
  return bytes < 1024 * 1024
    ? `${Math.round(bytes / 1024)} KB`
    : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}

/**
 * Whether to offer the workbook for download.
 *
 * A reviewer can pull any request's file — reviewing without seeing what is in it is
 * the gap this closes, now that the notification mail no longer attaches it.
 * Everyone else only ever sees their own requests here (core scopes the list), and
 * for them the file is theirs only once approved: offering it while pending would
 * hand over the data the approval is meant to gate.
 *
 * The same rules are enforced in core — this only decides whether to draw a button
 * that would work.
 */
function canDownload(request: ExportRequest, canReview: boolean): boolean {
  if (!request.file_size) return false;
  // Retention removed the file 24h after delivery; the row survives, the file doesn't.
  if (request.file_purged_at) return false;
  return canReview || request.status === "approved";
}

export default function ExportRequestsClientPage() {
  const [filter, setFilter] = useState<ExportRequestStatus | "all">("pending");
  const [items, setItems] = useState<ExportRequest[]>([]);
  const [canReview, setCanReview] = useState(false);
  const [loading, setLoading] = useState(true);
  const [busyId, setBusyId] = useState<string | null>(null);
  const [rejecting, setRejecting] = useState<ExportRequest | null>(null);
  const [note, setNote] = useState("");

  const load = useCallback(async () => {
    setLoading(true);
    const res = await listExportRequests({
      status: filter === "all" ? undefined : filter,
      limit: PAGE_SIZE,
    });
    if (res.success && res.data) {
      setItems(res.data.items);
      setCanReview(res.data.canReview);
    } else {
      setItems([]);
    }
    setLoading(false);
  }, [filter]);

  useEffect(() => {
    void load();
  }, [load]);

  const act = async (
    request: ExportRequest,
    action: "approve" | "reject",
    reviewNote?: string,
  ) => {
    setBusyId(request.id);
    const res = await reviewExportRequest(request.id, action, reviewNote);
    setBusyId(null);
    notifications.show({
      color: res.success ? (action === "approve" ? "green" : "gray") : "red",
      title: res.success
        ? action === "approve"
          ? "Approved"
          : "Rejected"
        : "Could not update",
      /*
       * Core's message is surfaced verbatim on success too, because approval can
       * partially succeed: the decision is recorded but the email may fail, and
       * that distinction matters to whoever is working the queue.
       */
      message: res.message,
      autoClose: 6000,
    });
    setRejecting(null);
    setNote("");
    await load();
  };

  return (
    <Container fluid px={0} py={0}>
      <Group justify="space-between" align="center" mb="lg">
        <Group gap="sm">
          <IconMailForward size={20} stroke={1.8} />
          <Title order={3}>Export Requests</Title>
          {!canReview && !loading && (
            <Badge variant="light" color="gray" size="sm">
              Your requests
            </Badge>
          )}
        </Group>
        <SegmentedControl
          size="xs"
          value={filter}
          onChange={(v) => setFilter(v as ExportRequestStatus | "all")}
          data={[
            { value: "pending", label: "Pending" },
            { value: "approved", label: "Approved" },
            { value: "rejected", label: "Rejected" },
            { value: "all", label: "All" },
          ]}
        />
      </Group>

      <Card className={styles.sectionCard} p="md">
        {loading ? (
          <Stack gap={6}>
            {Array.from({ length: 6 }, (_, i) => (
              <Skeleton key={i} height={36} radius="sm" />
            ))}
          </Stack>
        ) : items.length === 0 ? (
          <Text size="sm" c="dimmed" py="xl" ta="center">
            {filter === "pending"
              ? "Nothing waiting for approval."
              : "No requests to show."}
          </Text>
        ) : (
          <Table stickyHeader style={{ minWidth: 900 }}>
            <Table.Thead className={styles.tableHead}>
              <Table.Tr>
                <Table.Th className={styles.th}>Report</Table.Th>
                <Table.Th className={styles.th}>Requested by</Table.Th>
                <Table.Th className={styles.th}>Rows</Table.Th>
                <Table.Th className={styles.th}>File</Table.Th>
                <Table.Th className={styles.th}>Requested</Table.Th>
                <Table.Th className={styles.th}>Status</Table.Th>
                <Table.Th className={styles.th}>Reviewed</Table.Th>
                {canReview && <Table.Th className={styles.th}>Action</Table.Th>}
              </Table.Tr>
            </Table.Thead>
            <Table.Tbody>
              {items.map((r) => (
                <Table.Tr
                  key={r.id}
                  className={styles.row}
                  style={{ cursor: "default" }}
                >
                  <Table.Td>
                    <Stack gap={0}>
                      <Text size="sm" fw={600}>
                        {r.label}
                      </Text>
                      {r.filters_summary && (
                        <Text size="xs" c="dimmed" lineClamp={1}>
                          {r.filters_summary}
                        </Text>
                      )}
                    </Stack>
                  </Table.Td>
                  <Table.Td>{r.requested_by}</Table.Td>
                  <Table.Td className={styles.num}>
                    {r.row_count.toLocaleString()}
                  </Table.Td>
                  <Table.Td className={styles.num}>
                    {canDownload(r, canReview) ? (
                      /*
                       * A real anchor, not a fetch-and-blob: the browser streams
                       * the workbook to disk and takes its name from the
                       * response's Content-Disposition, and middle-click and
                       * "save link as" keep working.
                       */
                      <Button
                        component="a"
                        href={exportRequestFileUrl(r.id)}
                        download
                        variant="subtle"
                        size="compact-xs"
                        leftSection={<IconDownload size={13} />}
                        title={`Download ${r.filename}`}
                      >
                        {formatSize(r.file_size)}
                      </Button>
                    ) : r.file_purged_at ? (
                      /* Named rather than left as a silently missing button, so an
                         approver looking for last week's file knows the file is gone
                         by design and the record isn't broken. */
                      <Text size="xs" c="dimmed" title={`Deleted ${formatWhen(r.file_purged_at)}`}>
                        deleted
                      </Text>
                    ) : (
                      <Text size="sm" c="dimmed">
                        {formatSize(r.file_size)}
                      </Text>
                    )}
                  </Table.Td>
                  <Table.Td className={styles.num}>
                    {formatWhen(r.created_at)}
                  </Table.Td>
                  <Table.Td>
                    <Stack gap={2}>
                      <Badge
                        variant="light"
                        size="sm"
                        color={STATUS_COLOR[r.status]}
                      >
                        {r.status}
                      </Badge>
                      {/* An approval whose email failed is not the same as a
                          delivered one, so it is called out rather than shown as
                          a plain "approved". */}
                      {r.delivery_error && (
                        <Badge variant="light" size="xs" color="red">
                          email failed
                        </Badge>
                      )}
                      {r.status === "approved" && r.delivered_at && (
                        <Text size="xs" c="dimmed">
                          emailed
                        </Text>
                      )}
                    </Stack>
                  </Table.Td>
                  <Table.Td>
                    {/* Who decided, and when. The timestamp used to live in the
                        Action column, which meant a requester — who has no Action
                        column — could see that their export was actioned but not
                        when. It belongs with the reviewer either way. */}
                    <Stack gap={0}>
                      <Text size="xs">{r.reviewed_by ?? "—"}</Text>
                      {r.reviewed_at && (
                        <Text size="xs" c="dimmed">
                          {formatWhen(r.reviewed_at)}
                        </Text>
                      )}
                      {r.review_note && (
                        <Text size="xs" c="dimmed" lineClamp={1}>
                          {r.review_note}
                        </Text>
                      )}
                    </Stack>
                  </Table.Td>
                  {canReview && (
                    <Table.Td>
                      {r.status === "pending" ? (
                        <Group gap={6} wrap="nowrap">
                          <Button
                            size="compact-xs"
                            color="green"
                            variant="light"
                            leftSection={<IconCheck size={13} />}
                            loading={busyId === r.id}
                            onClick={() => void act(r, "approve")}
                          >
                            Approve
                          </Button>
                          <Button
                            size="compact-xs"
                            color="red"
                            variant="light"
                            leftSection={<IconX size={13} />}
                            disabled={busyId === r.id}
                            onClick={() => {
                              setRejecting(r);
                              setNote("");
                            }}
                          >
                            Reject
                          </Button>
                        </Group>
                      ) : (
                        <Text size="xs" c="dimmed">
                          —
                        </Text>
                      )}
                    </Table.Td>
                  )}
                </Table.Tr>
              ))}
            </Table.Tbody>
          </Table>
        )}
      </Card>

      <Modal
        opened={rejecting !== null}
        onClose={() => setRejecting(null)}
        title="Reject export request"
        centered
      >
        <Stack gap="sm">
          <Text size="sm">
            {rejecting?.label} — requested by {rejecting?.requested_by}
          </Text>
          {/* Optional, but the reason is stored on the request so the requester
              can see why rather than just that it was refused. */}
          <Textarea
            label="Reason (optional)"
            placeholder="Why is this being rejected?"
            value={note}
            onChange={(e) => setNote(e.currentTarget.value)}
            autosize
            minRows={2}
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setRejecting(null)}>
              Cancel
            </Button>
            <Button
              color="red"
              loading={busyId === rejecting?.id}
              onClick={() =>
                rejecting && void act(rejecting, "reject", note || undefined)
              }
            >
              Reject
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Container>
  );
}
