import {
  Alert,
  Badge,
  Box,
  Button,
  Card,
  Group,
  Loader,
  Modal,
  Stack,
  Text,
  Textarea,
  TextInput,
  ThemeIcon,
} from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import {
  ArrowRight,
  Ban,
  CircleAlert,
  Merge,
  Search,
  TriangleAlert,
} from "lucide-react";
import { useMemo, useState } from "react";

import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";

function parseError(error: unknown, fallback: string): string {
  if (isAxiosError(error)) {
    const message = error.response?.data?.message;
    if (Array.isArray(message)) return message.join(", ");
    if (typeof message === "string") return message;
  }
  return fallback;
}

const fmtDate = (iso: string) =>
  new Date(iso).toLocaleDateString("en-GB", {
    day: "numeric",
    month: "short",
    year: "numeric",
  });

export interface MergeScheduleTrainModalProps {
  scheduleId: string | null;
  /** This schedule's current train — excluded from the picker. */
  currentTrainId: string | null;
  scheduleReference?: string | null;
  opened: boolean;
  onClose: () => void;
  onMerged?: () => void;
}

/**
 * Merge another train into this schedule.
 *
 * This schedule always survives: its train set is repointed at the chosen
 * train, that train's wagons join this consist, and the emptied train is
 * deactivated. When the chosen train also runs a schedule on the SAME DAY, that
 * schedule's bookings move here and it is removed — its other-day schedules
 * gain the wagons only. The server computes all of that in `previewMerge`, so
 * the summary below is exactly what the commit will perform.
 */
export default function MergeScheduleTrainModal({
  scheduleId,
  currentTrainId,
  scheduleReference,
  opened,
  onClose,
  onMerged,
}: MergeScheduleTrainModalProps) {
  const { toast } = useToast();
  const [selectedTrainId, setSelectedTrainId] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [reason, setReason] = useState("");

  const { data: trains = [], isLoading: trainsLoading } = useQuery({
    ...api.trains.list.queryOptions(),
    enabled: opened,
  });

  // The schedule's own train cannot be merged into itself.
  const options = useMemo(() => {
    const q = search.trim().toLowerCase();
    return trains
      .filter((t) => t.id !== currentTrainId)
      .filter((t) =>
        q
          ? `${t.code} ${t.trainNumber ?? ""} ${t.trainName ?? ""}`
              .toLowerCase()
              .includes(q)
          : true,
      );
  }, [trains, currentTrainId, search]);

  const { data: preview, isFetching: previewLoading } = useQuery({
    ...api.trainScheduling.previewScheduleMerge.queryOptions({
      input: { id: scheduleId ?? "", targetTrainId: selectedTrainId ?? "" },
    }),
    enabled: opened && Boolean(scheduleId && selectedTrainId),
  });

  const merge = useMutation(api.trainScheduling.mergeScheduleTrain.mutationOptions());

  const close = () => {
    setSelectedTrainId(null);
    setSearch("");
    setReason("");
    onClose();
  };

  const submit = async () => {
    if (!scheduleId || !selectedTrainId || !preview?.canMerge) return;
    try {
      await merge.mutateAsync({
        id: scheduleId,
        targetTrainId: selectedTrainId,
        ...(reason.trim() ? { reason: reason.trim() } : {}),
      });
      toast({ title: "Trains merged" });
      onMerged?.();
      close();
    } catch (err) {
      toast({
        title: "Merge failed",
        description: parseError(err, "Could not merge the trains"),
        variant: "destructive",
      });
    }
  };

  return (
    <Modal
      opened={opened}
      onClose={close}
      centered
      size="lg"
      radius="lg"
      title={
        <Group gap="sm">
          <ThemeIcon variant="light" color="edr-green" radius="md" size={34}>
            <Merge size={18} />
          </ThemeIcon>
          <Box>
            <Text fw={600} lh={1.2}>
              Merge another train into this one
            </Text>
            <Text size="xs" c="dimmed" lh={1.2}>
              {scheduleReference ?? "This departure survives the merge"}
            </Text>
          </Box>
        </Group>
      }
    >
      <Stack gap="md">
        <TextInput
          placeholder="Search train code or number…"
          leftSection={<Search size={15} />}
          value={search}
          onChange={(e) => setSearch(e.currentTarget.value)}
          radius="md"
        />

        {trainsLoading ? (
          <Group justify="center" py="lg">
            <Loader size="sm" />
          </Group>
        ) : options.length === 0 ? (
          <Text size="sm" c="dimmed" py="sm">
            No other trains available to merge.
          </Text>
        ) : (
          <Box
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))",
              gap: 8,
              maxHeight: 190,
              overflowY: "auto",
            }}
          >
            {options.map((t) => {
              const on = t.id === selectedTrainId;
              return (
                <Card
                  key={t.id}
                  withBorder
                  radius="md"
                  padding="xs"
                  onClick={() => setSelectedTrainId(t.id)}
                  style={{
                    cursor: "pointer",
                    borderColor: on
                      ? "var(--mantine-color-edr-green-5)"
                      : undefined,
                    background: on
                      ? "var(--mantine-color-edr-green-0)"
                      : undefined,
                  }}
                >
                  <Text size="sm" fw={on ? 700 : 600} truncate>
                    {t.code}
                  </Text>
                  <Text size="xs" c="dimmed" truncate>
                    {t.trainNumber ? `No. ${t.trainNumber}` : "—"}
                  </Text>
                </Card>
              );
            })}
          </Box>
        )}

        {selectedTrainId && previewLoading ? (
          <Group justify="center" py="md">
            <Loader size="sm" />
          </Group>
        ) : null}

        {selectedTrainId && preview && !previewLoading ? (
          <Stack gap="sm">
            {preview.blockers.length ? (
              <Alert
                variant="light"
                color="red"
                icon={<CircleAlert size={16} />}
                title="This merge is blocked"
              >
                <Stack gap={4}>
                  {preview.blockers.map((b) => (
                    <Text size="sm" key={b}>
                      {b}
                    </Text>
                  ))}
                </Stack>
              </Alert>
            ) : (
              <Alert
                variant="light"
                color="orange"
                icon={<TriangleAlert size={16} />}
              >
                This cannot be undone. Wagons are appended last — reorder them
                afterwards in the train builder.
              </Alert>
            )}

            <Card withBorder radius="md" padding="sm">
              <Group gap={8} wrap="nowrap" mb={8}>
                <Text size="sm" fw={700}>
                  {preview.wagons.current} wagons
                </Text>
                <ArrowRight size={15} />
                <Text size="sm" fw={700} c="edr-green.8">
                  {preview.wagons.merged} wagons
                </Text>
                <Badge variant="light" color="edr-green" radius="sm">
                  +{preview.wagons.incoming} from {preview.targetTrain.code}
                </Badge>
              </Group>

              {preview.absorbedSchedule ? (
                <Group gap={6} wrap="nowrap" mb={6}>
                  <Badge size="sm" color="grape" variant="light" radius="sm">
                    {fmtDate(preview.absorbedSchedule.scheduledDepartureDate)}
                  </Badge>
                  <Text size="sm">
                    {preview.absorbedSchedule.reference ?? "Same-day schedule"} —{" "}
                    <Text span fw={700}>
                      {preview.absorbedSchedule.bookingsMoving} booking(s)
                    </Text>{" "}
                    move here, then it is removed
                  </Text>
                </Group>
              ) : null}

              {preview.affectedSchedules.map((s) => (
                <Group gap={6} wrap="nowrap" mb={4} key={s.id}>
                  <Badge size="sm" color="blue" variant="light" radius="sm">
                    {fmtDate(s.scheduledDepartureDate)}
                  </Badge>
                  <Text size="sm" c="dimmed">
                    {s.reference ?? s.id.slice(0, 8)} — gains the wagons, keeps
                    its own bookings
                  </Text>
                </Group>
              ))}

              {preview.untouchedSchedules.map((s) => (
                <Group gap={6} wrap="nowrap" mb={4} key={s.id}>
                  <Badge size="sm" color="gray" variant="light" radius="sm">
                    {fmtDate(s.scheduledDepartureDate)}
                  </Badge>
                  <Text size="sm" c="dimmed">
                    {s.reference ?? s.id.slice(0, 8)} — {s.status.toLowerCase()},
                    not affected
                  </Text>
                </Group>
              ))}

              {preview.sourceTrainWillDeactivate ? (
                <Group gap={6} mt={6} wrap="nowrap">
                  <Ban size={14} color="var(--mantine-color-red-6)" />
                  <Text size="sm" c="red.7">
                    This schedule&apos;s current train is emptied and deactivated.
                  </Text>
                </Group>
              ) : null}
            </Card>

            {preview.canMerge ? (
              <Textarea
                label="Reason"
                placeholder="Why the trains are being merged (kept on the audit trail)"
                maxLength={500}
                autosize
                minRows={2}
                value={reason}
                onChange={(e) => setReason(e.currentTarget.value)}
              />
            ) : null}
          </Stack>
        ) : null}

        <Group justify="flex-end" mt="xs">
          <Button variant="default" onClick={close}>
            Cancel
          </Button>
          <Button
            color="edr-green"
            leftSection={<Merge size={16} />}
            loading={merge.isPending}
            disabled={!preview?.canMerge || previewLoading}
            onClick={() => void submit()}
          >
            Merge trains
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}
