import { Fragment, useMemo, useState } from "react";
import {
  ActionIcon,
  Badge,
  Box,
  Group,
  Paper,
  SimpleGrid,
  Skeleton,
  Stack,
  Text,
} from "@mantine/core";
import {
  ArrowRight,
  CalendarClock,
  CheckCircle2,
  ChevronLeft,
  ChevronRight,
  Clock,
} from "lucide-react";
import { CountdownTimer, bookingWindowUiState } from "@edr/ui-common";
import type { BookingWindowUiKind } from "@edr/ui-common";

import type { MyBookingWindow } from "@/services/bookings.service";
import {
  formatWindowOpensAt,
  soonestUpcomingWindow,
  windowRouteStops,
} from "./booking-window";

const INK = "#10202F";
const MUTED = "#6B7C8E";
const BORDER = "#E6ECF2";

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

/** Customer-facing labels for a booking-window phase / status. */
const WINDOW_PHASE_LABELS: Record<string, string> = {
  PRE_WINDOW: "Opens soon",
  OPEN: "Open now",
  DOC_REVIEW: "Document review",
  PAYMENT: "Payment due",
  DONE: "Closed",
  CLOSED_FOR_DAY: "Closed for the day",
};

/** Friendly label for a window phase/status, never the raw enum. */
function windowPhaseLabel(phase?: string | null): string {
  if (!phase) return "—";
  return (
    WINDOW_PHASE_LABELS[phase] ??
    phase
      .replace(/_/g, " ")
      .toLowerCase()
      .replace(/\b\w/g, (m) => m.toUpperCase())
  );
}

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,
  });
}

/** "Thu, 10 Jul · 08:00 – 11:00 EAT" (or a phase label when times are unset). */
function windowLabel(w: MyBookingWindow): 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 windowPhaseLabel(w.windowPhase ?? w.bookingWindowStatus);
}

/**
 * The countdown for the window's UI state, mirroring the home dashboard's
 * Booking Windows card. Derived from the SAME state as the badge
 * (`bookingWindowUiState`) so they can never contradict — a full train shows
 * no ticking countdown. `expiredText` names the NEXT step so a deadline that
 * lapses between refetches announces what comes next rather than the bare
 * "Expired".
 */
const COUNTDOWN_TEXT: Partial<
  Record<BookingWindowUiKind, { label: string; expiredText: string }>
> = {
  PRE_WINDOW: { label: "Booking opens in", expiredText: "Booking opening now…" },
  OPEN: { label: "Window closes in", expiredText: "Document review starting…" },
  DOC_REVIEW: { label: "Document review ends in", expiredText: "Payment starting…" },
  PAYMENT: { label: "Payment due in", expiredText: "Payment window closing…" },
};

function phaseCountdown(
  w: MyBookingWindow,
): { label: string; deadline: string; expiredText: string } | null {
  const state = bookingWindowUiState(w);
  const text = COUNTDOWN_TEXT[state.kind];
  if (!state.countdownTo || !text) return null;
  return { ...text, deadline: state.countdownTo };
}

/**
 * Drop windows the SERVER considers finished. Keyed off the server's windowPhase
 * — never the client clock. The server query already excludes terminal
 * (DONE / CLOSED_FOR_DAY) and departed rows; comparing `Date.now()` against the
 * row's timestamps here only re-introduced clock skew, which made a card vanish
 * on one machine and reappear after refresh. So we trust the phase the server
 * sends (live-patched over the socket) and let it drive visibility.
 */
function isPast(w: MyBookingWindow): boolean {
  return w.windowPhase === "DONE" || w.windowPhase === "CLOSED_FOR_DAY";
}

/** Badge label + Mantine color per UI state — same state the countdown uses. */
const KIND_BADGE: Record<BookingWindowUiKind, { label: string; color: string }> = {
  OPEN: { label: "Open now", color: "edr-green" },
  FULL: { label: "Train full", color: "red" },
  PRE_WINDOW: { label: "Opens soon", color: "yellow" },
  DOC_REVIEW: { label: "Document review", color: "gray" },
  PAYMENT: { label: "Payment due", color: "gray" },
  CLOSED: { label: "Closed", color: "gray" },
};

function WindowCard({ w }: { w: MyBookingWindow }) {
  const cd = phaseCountdown(w);
  const state = bookingWindowUiState(w);
  const badge = KIND_BADGE[state.kind];
  const open = state.isBookable;
  const isImport = w.direction === "IMPORT";

  return (
    <Box
      p="md"
      style={{
        borderRadius: 14,
        height: "100%",
        border: `1px solid ${open ? "#CDEBDD" : BORDER}`,
        background: open
          ? "linear-gradient(160deg, #F4FBF7 0%, #FFFFFF 85%)"
          : "#FFFFFF",
        boxShadow: open ? "0 2px 10px rgba(10,111,77,0.10)" : "none",
      }}
    >
      <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={badge.color}
              radius="sm"
              size="sm"
            >
              {badge.label}
            </Badge>
          </Group>

          <Group gap={6} wrap="wrap" mt={10}>
            {windowRouteStops(w).map((stop, i) => (
              <Fragment key={i}>
                {i > 0 ? (
                  <ArrowRight
                    size={14}
                    color={MUTED}
                    style={{ flexShrink: 0 }}
                  />
                ) : null}
                <Text fz={15} fw={700} style={{ color: INK }}>
                  {stop}
                </Text>
              </Fragment>
            ))}
          </Group>

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

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

/**
 * One-line status strip above the cards: green when a window is open right now
 * (the customer can act), neutral with the next opening time otherwise.
 */
function WindowStatusBanner({ windows }: { windows: MyBookingWindow[] }) {
  const open = windows.find((w) => w.isOpenNow);
  if (open) {
    const lane =
      open.origin && open.destination
        ? ` on ${open.origin} → ${open.destination}`
        : "";
    return (
      <Group
        gap={10}
        wrap="nowrap"
        px={14}
        py={10}
        mb="md"
        style={{
          borderRadius: 12,
          border: "1px solid #CDEBDD",
          background: "#F4FBF7",
        }}
      >
        <CheckCircle2 size={17} color="#0A6F4D" style={{ flexShrink: 0 }} />
        <Text fz={13.5} fw={600} c="#0A6F4D">
          A booking window is open right now{lane} — you can create a shipment
          booking before it closes.
        </Text>
      </Group>
    );
  }

  const next = soonestUpcomingWindow(windows);
  return (
    <Group
      gap={10}
      wrap="nowrap"
      px={14}
      py={10}
      mb="md"
      style={{
        borderRadius: 12,
        border: `1px solid ${BORDER}`,
        background: "#F8FAFC",
      }}
    >
      <Clock size={17} color={MUTED} style={{ flexShrink: 0 }} />
      <Text fz={13.5} fw={600} style={{ color: MUTED }}>
        {next?.windowOpensAt
          ? `Booking is not open yet — the next window opens ${formatWindowOpensAt(
              next.windowOpensAt,
            )} EAT.`
          : "Booking is not open right now. You'll see the opening time here once a window is announced."}
      </Text>
    </Group>
  );
}

interface ContractBookingWindowsSectionProps {
  /** Windows already scoped to this contract's routes/direction by the API. */
  windows: MyBookingWindow[];
  isLoading: boolean;
}

/**
 * Booking windows on THIS contract's routes only (the API filters by the
 * contract's route lanes, which also pins the import/export direction) — the
 * contract-scoped counterpart of the home dashboard's all-lanes Booking Windows
 * card. Paged three cards at a time; hidden when nothing is announced.
 */
export function ContractBookingWindowsSection({
  windows,
  isLoading,
}: ContractBookingWindowsSectionProps) {
  const [page, setPage] = useState(0);

  const sorted = useMemo(() => {
    const rows = windows.filter(
      (w) => w.windowPhase != null && w.windowPhase !== "DONE" && !isPast(w),
    );
    // Order by the train's dispatch (departure) date, nearest first — the
    // shipment leaving soonest leads. 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);
    });
  }, [windows]);

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

  return (
    <Paper withBorder radius="lg" p="lg" style={{ borderColor: BORDER }}>
      <Group justify="space-between" align="flex-start" mb="md" wrap="nowrap">
        <Group gap={8} wrap="nowrap">
          <CalendarClock size={18} color={MUTED} />
          <Box>
            <Text fw={700} fz={16} style={{ color: INK }}>
              Booking windows
            </Text>
            <Text fz={13} style={{ color: MUTED }}>
              Windows on this contract&apos;s routes (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 ? "#0A6F4D" : "#D8E2EB",
                    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>
      ) : sorted.length === 0 ? (
        <Stack
          align="center"
          gap={6}
          py={28}
          style={{
            borderRadius: 12,
            border: `1px dashed ${BORDER}`,
            background: "#FBFCFE",
          }}
        >
          <CalendarClock size={22} color={MUTED} />
          <Text fz={14} fw={600} style={{ color: INK }}>
            No booking windows announced yet
          </Text>
          <Text fz={12.5} ta="center" maw={420} style={{ color: MUTED }}>
            When a train is scheduled on this contract&apos;s routes, its
            booking window will appear here with the opening time.
          </Text>
        </Stack>
      ) : (
        <>
          <WindowStatusBanner windows={sorted} />
          <SimpleGrid
            key={safePage}
            cols={{ base: 1, sm: 2, lg: 3 }}
            spacing="md"
          >
            {visible.map((w) => (
              <WindowCard key={`${w.scheduleId}-${w.bookingCycleNo}`} w={w} />
            ))}
          </SimpleGrid>
        </>
      )}
    </Paper>
  );
}
