import { useMemo, useState } from "react";
import {
  ActionIcon,
  Badge,
  Box,
  Card,
  Group,
  SimpleGrid,
  Skeleton,
  Stack,
  Text,
} from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import {
  ArrowRight,
  CalendarClock,
  ChevronLeft,
  ChevronRight,
} from "lucide-react";
import { CountdownTimer } from "@edr/ui-common";

import { useBookingWindowSocket } from "@/features/bookingWindows/useBookingWindowSocket";
import { api } from "@/services/api";

/**
 * The fields a window card needs. Structural so both `StaffBookingWindow`
 * (all-lanes staff feed) and `BookingWindow` (contract-scoped feed, which
 * carries no train number) satisfy it.
 */
interface WindowRow {
  scheduleId: string;
  reference?: string | null;
  trainNumber?: string | null;
  direction: string | null;
  windowPhase: string | null;
  isOpenNow: boolean;
  windowOpensAt: string | null;
  windowClosesAt: string | null;
  docReviewEndsAt: string | null;
  paymentPhaseEndsAt: string | null;
  bookingWindowStatus: string;
  bookingCycleNo: number;
  departureDate: string;
  origin: string | null;
  destination: string | null;
}

/** All window times are communicated in East Africa Time. */
const TZ = "Africa/Addis_Ababa";
/** Cards visible per carousel page. */
const PER_PAGE = 3;

function fmtDay(iso: string): string {
  return new Date(iso).toLocaleDateString("en-GB", {
    weekday: "short",
    day: "numeric",
    month: "short",
    timeZone: TZ,
  });
}

function fmtTime(iso: string): string {
  return new Date(iso).toLocaleTimeString("en-GB", {
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
    timeZone: TZ,
  });
}

function windowLabel(w: WindowRow): string {
  if (w.windowOpensAt && w.windowClosesAt) {
    return `${fmtDay(w.windowOpensAt)} · ${fmtTime(w.windowOpensAt)} – ${fmtTime(
      w.windowClosesAt,
    )} EAT`;
  }
  if (w.windowOpensAt) {
    return `Opens ${fmtDay(w.windowOpensAt)} · ${fmtTime(w.windowOpensAt)} EAT`;
  }
  return (w.windowPhase ?? w.bookingWindowStatus).replace(/_/g, " ");
}

/**
 * The countdown for whichever phase the window is currently in, mirroring the
 * customer portal. `expiredText` names the NEXT step so a deadline that lapses
 * between refetches announces what comes next rather than the bare "Expired".
 */
function phaseCountdown(
  w: WindowRow,
): { label: string; deadline: string; expiredText: string } | null {
  switch (w.windowPhase) {
    case "PRE_WINDOW":
      return w.windowOpensAt
        ? {
            label: "Opens in",
            deadline: w.windowOpensAt,
            expiredText: "Opening now…",
          }
        : null;
    case "OPEN":
      return w.windowClosesAt
        ? {
            label: "Closes in",
            deadline: w.windowClosesAt,
            expiredText: "Review starting…",
          }
        : null;
    case "DOC_REVIEW":
      return w.docReviewEndsAt
        ? {
            label: "Doc review ends in",
            deadline: w.docReviewEndsAt,
            expiredText: "Payment starting…",
          }
        : null;
    case "PAYMENT":
      return w.paymentPhaseEndsAt
        ? {
            label: "Payment ends in",
            deadline: w.paymentPhaseEndsAt,
            expiredText: "Closing…",
          }
        : null;
    default:
      return null;
  }
}

/**
 * Drop windows the SERVER considers finished — keyed off windowPhase, never the
 * client clock. The server query already excludes terminal / departed rows;
 * comparing `Date.now()` here only re-introduced clock skew that made a card
 * vanish and reappear on refresh. Trust the server phase (live-patched over the
 * socket) instead.
 */
function isPast(w: WindowRow): boolean {
  return w.windowPhase === "DONE" || w.windowPhase === "CLOSED_FOR_DAY";
}

function WindowCard({ w }: { w: WindowRow }) {
  const cd = phaseCountdown(w);
  const open = w.isOpenNow;
  const isImport = w.direction === "IMPORT";

  return (
    <Box
      p="md"
      style={{
        borderRadius: 14,
        height: "100%",
        border: `1px solid ${
          open
            ? "var(--mantine-color-edr-green-3)"
            : "var(--mantine-color-gray-2)"
        }`,
        background: open
          ? "linear-gradient(160deg, var(--mantine-color-edr-green-0) 0%, #ffffff 85%)"
          : "var(--mantine-color-body)",
        boxShadow: open ? "0 2px 10px rgba(10,111,77,0.10)" : "none",
        transition: "border-color 150ms ease, box-shadow 150ms ease",
      }}
    >
      <Stack gap={8} h="100%" justify="space-between">
        <Box>
          <Group justify="space-between" wrap="nowrap" gap={8}>
            {w.direction ? (
              <Badge
                variant="light"
                color={isImport ? "blue" : "teal"}
                radius="sm"
                size="sm"
              >
                {isImport ? "Import" : "Export"}
              </Badge>
            ) : (
              <span />
            )}
            <Badge
              variant={open ? "filled" : "light"}
              color={open ? "edr-green" : "gray"}
              radius="sm"
              size="sm"
            >
              {open
                ? "Open now"
                : (w.windowPhase ?? w.bookingWindowStatus).replace(/_/g, " ")}
            </Badge>
          </Group>

          <Group gap={6} wrap="nowrap" mt={10}>
            <Text fz={15} fw={700} truncate>
              {w.origin ?? "—"}
            </Text>
            <ArrowRight size={14} style={{ flexShrink: 0, opacity: 0.5 }} />
            <Text fz={15} fw={700} truncate>
              {w.destination ?? "—"}
            </Text>
          </Group>
          {w.reference ? (
            <Text fz={12} fw={600} ff="monospace" c="edr-green.7" truncate>
              {w.reference}
            </Text>
          ) : null}
          {w.trainNumber ? (
            <Text fz={12} c="dimmed" truncate>
              Train {w.trainNumber}
            </Text>
          ) : null}

          <Group gap={6} wrap="nowrap" mt={8}>
            <CalendarClock size={13} style={{ flexShrink: 0, opacity: 0.6 }} />
            <Text fz={12} c="dimmed" truncate>
              {windowLabel(w)}
            </Text>
          </Group>
          {w.departureDate ? (
            <Text fz={12} c="dimmed">
              Departs {fmtDay(w.departureDate)}
            </Text>
          ) : null}
        </Box>

        {cd ? (
          <Box
            px={10}
            py={6}
            style={{
              borderRadius: 10,
              background: open
                ? "rgba(10,111,77,0.08)"
                : "var(--mantine-color-gray-0)",
            }}
          >
            <CountdownTimer
              deadline={cd.deadline}
              label={cd.label}
              expiredText={cd.expiredText}
              size="xs"
            />
          </Box>
        ) : null}
      </Stack>
    </Box>
  );
}

interface GlUpcomingWindowsSectionProps {
  /**
   * Scope the card to one contract: only windows on that contract's routes
   * (and therefore its import/export direction) are shown. Omit for the
   * all-lanes staff feed on the clearance queue.
   */
  contractId?: string;
}

/**
 * Announced booking windows (import cycles + export FCFS) as a paged carousel —
 * three lanes per page, arrows to flip. Without `contractId` it shows every
 * lane (GL ET clearance queue); with `contractId` it shows only the windows
 * matching that contract's routes/direction (clearance detail page). Mirrors
 * the customer's portal "Booking Windows" card. Hidden when nothing is pending.
 */
export function GlUpcomingWindowsSection({
  contractId,
}: GlUpcomingWindowsSectionProps = {}) {
  // Live pushes flip cards the moment the window engine transitions a phase;
  // the 60s poll below stays only as a fallback.
  useBookingWindowSocket();
  const allLanes = useQuery({
    ...api.trainScheduling.allBookingWindows.queryOptions({
      refetchInterval: 60_000,
    }),
    enabled: !contractId,
  });
  const contractLanes = useQuery({
    ...api.trainScheduling.contractBookingWindows.queryOptions({
      input: { contractId: contractId ?? "" },
      refetchInterval: 60_000,
    }),
    enabled: Boolean(contractId),
  });
  const data: WindowRow[] | undefined = contractId
    ? contractLanes.data
    : allLanes.data;
  const isLoading = contractId ? contractLanes.isLoading : allLanes.isLoading;
  const [page, setPage] = useState(0);

  const windows = useMemo(() => {
    const rows = (data ?? []).filter(
      (w) => w.windowPhase != null && w.windowPhase !== "DONE" && !isPast(w),
    );
    // Canceled schedules are retired to windowPhase='DONE' server-side, so the
    // guard above already excludes them; they never reach the upcoming list.
    // Order by the train's dispatch (departure) date, nearest first. Open-now
    // breaks ties on the same departure.
    return rows.sort((a, b) => {
      const da = a.departureDate ? new Date(a.departureDate).getTime() : Infinity;
      const db = b.departureDate ? new Date(b.departureDate).getTime() : Infinity;
      if (da !== db) return da - db;
      return Number(b.isOpenNow) - Number(a.isOpenNow);
    });
  }, [data]);

  const pageCount = Math.max(1, Math.ceil(windows.length / PER_PAGE));
  const safePage = Math.min(page, pageCount - 1);
  const visible = windows.slice(
    safePage * PER_PAGE,
    safePage * PER_PAGE + PER_PAGE,
  );

  if (!isLoading && windows.length === 0) return null;

  return (
    <Card withBorder shadow="sm" radius="lg" p="lg">
      <Group justify="space-between" align="flex-start" mb="md" wrap="nowrap">
        <Group gap={8} wrap="nowrap">
          <CalendarClock size={18} />
          <Box>
            <Text fw={700} fz={16}>
              Booking windows
            </Text>
            <Text fz={13} c="dimmed">
              {contractId
                ? "Booking windows on this contract's routes (EAT)"
                : "Import and export booking windows across all lanes (EAT)"}
            </Text>
          </Box>
        </Group>

        {pageCount > 1 ? (
          <Group gap={8} wrap="nowrap">
            <ActionIcon
              variant="default"
              radius="xl"
              size="lg"
              aria-label="Previous windows"
              disabled={safePage <= 0}
              onClick={() => setPage((p) => Math.max(0, p - 1))}
            >
              <ChevronLeft size={18} />
            </ActionIcon>
            <Group gap={5} wrap="nowrap">
              {Array.from({ length: pageCount }, (_, i) => (
                <Box
                  key={i}
                  onClick={() => setPage(i)}
                  style={{
                    width: i === safePage ? 18 : 7,
                    height: 7,
                    borderRadius: 999,
                    cursor: "pointer",
                    background:
                      i === safePage
                        ? "var(--mantine-color-edr-green-6)"
                        : "var(--mantine-color-gray-3)",
                    transition: "width 200ms ease, background 200ms ease",
                  }}
                />
              ))}
            </Group>
            <ActionIcon
              variant="default"
              radius="xl"
              size="lg"
              aria-label="Next windows"
              disabled={safePage >= pageCount - 1}
              onClick={() => setPage((p) => Math.min(pageCount - 1, p + 1))}
            >
              <ChevronRight size={18} />
            </ActionIcon>
          </Group>
        ) : null}
      </Group>

      {isLoading ? (
        <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
          {[1, 2, 3].map((i) => (
            <Skeleton key={i} height={150} radius="md" />
          ))}
        </SimpleGrid>
      ) : (
        <SimpleGrid key={safePage} cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
          {visible.map((w) => (
            <WindowCard key={`${w.scheduleId}-${w.bookingCycleNo}`} w={w} />
          ))}
        </SimpleGrid>
      )}
    </Card>
  );
}
