import {
  Badge,
  Box,
  Button,
  Code,
  Divider,
  Grid,
  Group,
  Loader,
  Modal,
  Pagination,
  Paper,
  ScrollArea,
  Stack,
  Text,
  Timeline,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconArrowRight, IconHistory } from "@tabler/icons-react";
import moment from "moment";
import { useCallback, useEffect, useState } from "react";
import type { AdminActivityLog } from "@/lib/features/activity-logs/query";
import { getCampaignLogs } from "@/lib/features/promo-code-campaigns/query";

// ── diff helpers ──────────────────────────────────────────────────────────────

type DiffType = "added" | "removed" | "changed";
interface DiffEntry {
  path: string;
  type: DiffType;
  before: any;
  after: any;
}

function flattenObject(obj: any, prefix = ""): Record<string, any> {
  if (obj === null || obj === undefined || typeof obj !== "object") {
    return prefix ? { [prefix]: obj } : {};
  }
  if (Array.isArray(obj)) return { [prefix]: JSON.stringify(obj) };
  return Object.entries(obj).reduce<Record<string, any>>((acc, [k, v]) => {
    const key = prefix ? `${prefix}.${k}` : k;
    if (v !== null && typeof v === "object" && !Array.isArray(v)) {
      Object.assign(acc, flattenObject(v, key));
    } else {
      acc[key] = Array.isArray(v) ? JSON.stringify(v) : v;
    }
    return acc;
  }, {});
}

function diffObjects(before: any, after: any): DiffEntry[] {
  const flatBefore = flattenObject(before ?? {});
  const flatAfter = flattenObject(after ?? {});
  const allKeys = new Set([
    ...Object.keys(flatBefore),
    ...Object.keys(flatAfter),
  ]);
  const entries: DiffEntry[] = [];
  for (const key of Array.from(allKeys).sort()) {
    const bVal = flatBefore[key];
    const aVal = flatAfter[key];
    if (!(key in flatBefore))
      entries.push({
        path: key,
        type: "added",
        before: undefined,
        after: aVal,
      });
    else if (!(key in flatAfter))
      entries.push({
        path: key,
        type: "removed",
        before: bVal,
        after: undefined,
      });
    else if (String(bVal) !== String(aVal))
      entries.push({ path: key, type: "changed", before: bVal, after: aVal });
  }
  return entries;
}

function parseJson(val: any) {
  try {
    return typeof val === "string" ? JSON.parse(val) : val;
  } catch {
    return null;
  }
}

function formatValue(val: any): string {
  if (val === null || val === undefined) return "—";
  if (typeof val === "object") {
    try {
      return JSON.stringify(val, null, 2);
    } catch {
      return String(val);
    }
  }
  return String(val);
}

function isComplex(val: any): boolean {
  return val !== null && typeof val === "object";
}

function ValueCell({
  val,
  tone,
  label,
}: {
  val: any;
  tone: "red" | "green" | "neutral";
  label: string;
}) {
  const [expanded, setExpanded] = useState(false);
  const formatted = formatValue(val);
  const isLong = formatted.length > 180;
  const display =
    !expanded && isLong ? `${formatted.slice(0, 180)}…` : formatted;
  const bg =
    tone === "red"
      ? "var(--mantine-color-red-0)"
      : tone === "green"
        ? "var(--mantine-color-green-0)"
        : "var(--mantine-color-gray-0)";
  const borderColor =
    tone === "red"
      ? "var(--mantine-color-red-3)"
      : tone === "green"
        ? "var(--mantine-color-green-3)"
        : "var(--mantine-color-gray-3)";
  const labelColor =
    tone === "red" ? "red" : tone === "green" ? "green" : "dimmed";

  return (
    <Box
      style={{
        background: bg,
        border: `1px solid ${borderColor}`,
        borderRadius: 6,
        padding: "8px 10px",
        height: "100%",
        minHeight: 48,
      }}
    >
      <Text
        size="xs"
        fw={600}
        c={labelColor}
        tt="uppercase"
        mb={4}
        style={{ letterSpacing: "0.05em" }}
      >
        {label}
      </Text>
      {isComplex(val) ? (
        <>
          <Code
            block
            style={{
              fontSize: 11,
              background: "transparent",
              padding: 0,
              whiteSpace: "pre-wrap",
              wordBreak: "break-all",
            }}
          >
            {display}
          </Code>
          {isLong && (
            <Button
              size="compact-xs"
              variant="subtle"
              mt={4}
              onClick={() => setExpanded((e) => !e)}
            >
              {expanded ? "Show less" : "Show more"}
            </Button>
          )}
        </>
      ) : (
        <>
          <Text size="sm" style={{ wordBreak: "break-all" }}>
            {display}
          </Text>
          {isLong && (
            <Button
              size="compact-xs"
              variant="subtle"
              mt={4}
              onClick={() => setExpanded((e) => !e)}
            >
              {expanded ? "Show less" : "Show more"}
            </Button>
          )}
        </>
      )}
    </Box>
  );
}

function DiffRow({ entry }: { entry: DiffEntry }) {
  const accentColor =
    entry.type === "added"
      ? "green"
      : entry.type === "removed"
        ? "red"
        : "blue";
  return (
    <Paper
      withBorder
      radius="md"
      p="sm"
      style={{ borderLeft: `4px solid var(--mantine-color-${accentColor}-5)` }}
    >
      <Group gap="xs" mb={8}>
        <Text size="xs" fw={700} ff="monospace" c="dark">
          {entry.path}
        </Text>
        <Badge size="xs" color={accentColor} variant="filled" radius="sm">
          {entry.type === "added"
            ? "new field"
            : entry.type === "removed"
              ? "removed"
              : "changed"}
        </Badge>
      </Group>
      {entry.type === "changed" ? (
        <Grid gutter="sm">
          <Grid.Col span={5}>
            <ValueCell val={entry.before} tone="red" label="Before" />
          </Grid.Col>
          <Grid.Col
            span={2}
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
            }}
          >
            <IconArrowRight size={18} color="var(--mantine-color-gray-5)" />
          </Grid.Col>
          <Grid.Col span={5}>
            <ValueCell val={entry.after} tone="green" label="After" />
          </Grid.Col>
        </Grid>
      ) : entry.type === "added" ? (
        <ValueCell val={entry.after} tone="green" label="New value" />
      ) : (
        <ValueCell val={entry.before} tone="red" label="Removed value" />
      )}
    </Paper>
  );
}

// Noise fields that are either redundant (already shown in the timeline / page
// context) or pure DB plumbing. Drop them from the modal display.
const NOISE_FIELDS = new Set([
  "id",
  "campaign_id",
  "created_at",
  "created_by",
  "updated_at",
  "updated_by",
]);

function omitNoise(obj: any): any {
  if (!obj || typeof obj !== "object") return obj;
  if (Array.isArray(obj)) return obj.map(omitNoise);
  const out: Record<string, any> = {};
  for (const [k, v] of Object.entries(obj)) {
    if (!NOISE_FIELDS.has(k)) out[k] = v;
  }
  return out;
}

function DetailsModal({
  log,
  opened,
  onClose,
}: {
  log: AdminActivityLog | null;
  opened: boolean;
  onClose: () => void;
}) {
  if (!log) return null;
  const before = omitNoise(parseJson(log.before_payload));
  const after = omitNoise(parseJson(log.payload));
  // PATCH/PUT bodies typically contain only the changed fields. Anything in
  // before but not in after wasn't removed — it just wasn't part of this
  // update — so drop those false "removed" entries.
  const changes =
    log.method === "DELETE"
      ? []
      : diffObjects(before ?? {}, after ?? {}).filter(
          (d) => d.type !== "removed",
        );
  const hasBefore = !!before;
  // For a DELETE the removed rows come from the response (the request body just
  // holds the ids that were removed — not "new values"). Resolve them once so we
  // render only a "Removed record(s)" section, never "Fields updated".
  const removed =
    log.method === "DELETE"
      ? (before ?? omitNoise(parseJson(log.response_payload)?.data) ?? null)
      : null;
  const hasRemoved = !!removed && Object.keys(removed).length > 0;
  const methodColor =
    log.method === "DELETE" ? "red" : log.method === "POST" ? "green" : "blue";

  return (
    <Modal
      opened={opened}
      onClose={onClose}
      title={
        <Group gap="xs">
          <Text fw={700} size="md">
            Change Details
          </Text>
          <Badge color={methodColor} variant="filled" size="sm" radius="sm">
            {log.method}
          </Badge>
        </Group>
      }
      size="70rem"
      centered
      scrollAreaComponent={ScrollArea.Autosize}
    >
      <Stack gap="md">
        <Group gap="md">
          <Text size="sm" c="dimmed">
            {moment(log.performed_at).format("DD MMM YYYY, HH:mm")}
          </Text>
          <Text size="xs" c="dimmed">
            ·
          </Text>
          <Text size="sm" c="dimmed">
            {log.admin_email}
          </Text>
        </Group>
        {log.url && (
          <Text
            size="xs"
            c="dimmed"
            ff="monospace"
            style={{ wordBreak: "break-all" }}
          >
            {log.method} {log.url}
          </Text>
        )}
        <Divider />

        {log.method === "POST" && after && (
          <Stack gap="xs">
            <Text size="sm" fw={600} c="green">
              Created with {Object.keys(after).length} fields
            </Text>
            {Object.entries(after).map(([k, v]) => (
              <Paper
                key={k}
                withBorder
                radius="md"
                p="sm"
                style={{ borderLeft: "4px solid var(--mantine-color-green-5)" }}
              >
                <Text size="xs" fw={700} ff="monospace" mb={6}>
                  {k}
                </Text>
                <ValueCell val={v} tone="green" label="Value" />
              </Paper>
            ))}
          </Stack>
        )}

        {hasBefore && changes.length > 0 && (
          <Stack gap="xs">
            <Group gap="xs">
              <Text size="sm" fw={600}>
                {changes.length} field{changes.length !== 1 ? "s" : ""} changed
              </Text>
              <Badge size="xs" color="blue" variant="light">
                {changes.filter((d) => d.type === "changed").length} modified
              </Badge>
              {changes.filter((d) => d.type === "added").length > 0 && (
                <Badge size="xs" color="green" variant="light">
                  {changes.filter((d) => d.type === "added").length} added
                </Badge>
              )}
              {changes.filter((d) => d.type === "removed").length > 0 && (
                <Badge size="xs" color="red" variant="light">
                  {changes.filter((d) => d.type === "removed").length} removed
                </Badge>
              )}
            </Group>
            {changes.map((d) => (
              <DiffRow key={d.path} entry={d} />
            ))}
          </Stack>
        )}

        {!hasBefore &&
          after &&
          log.method !== "POST" &&
          log.method !== "DELETE" && (
            <Stack gap="xs">
              <Text size="sm" fw={600} c="dimmed">
                Fields updated{" "}
                <Text span size="xs">
                  (no snapshot — before values unavailable)
                </Text>
              </Text>
              {Object.entries(after).map(([k, v]) => (
                <Paper
                  key={k}
                  withBorder
                  radius="md"
                  p="sm"
                  style={{
                    borderLeft: "4px solid var(--mantine-color-blue-5)",
                  }}
                >
                  <Text size="xs" fw={700} ff="monospace" mb={6}>
                    {k}
                  </Text>
                  <ValueCell val={v} tone="green" label="New value" />
                </Paper>
              ))}
            </Stack>
          )}

        {log.method === "DELETE" && hasRemoved && (
          <Stack gap="xs">
            <Text size="sm" fw={600} c="red">
              {Array.isArray(removed)
                ? `Removed ${removed.length} record${removed.length !== 1 ? "s" : ""}`
                : "Removed record"}
            </Text>
            {Object.entries(removed).map(([k, v]) => (
              <Paper
                key={k}
                withBorder
                radius="md"
                p="sm"
                style={{ borderLeft: "4px solid var(--mantine-color-red-5)" }}
              >
                <Text size="xs" fw={700} ff="monospace" mb={6}>
                  {Array.isArray(removed) ? `#${k}` : k}
                </Text>
                <ValueCell val={v} tone="red" label="Value" />
              </Paper>
            ))}
          </Stack>
        )}

        {!after && !before && !hasRemoved && (
          <Text size="sm" c="dimmed">
            No payload details available.
          </Text>
        )}
      </Stack>
    </Modal>
  );
}

// ── Panel ─────────────────────────────────────────────────────────────────────

interface Props {
  campaignId?: string | null;
}

const PAGE_SIZE = 100;

export function CampaignLogsPanel({ campaignId }: Props) {
  const [logs, setLogs] = useState<AdminActivityLog[]>([]);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);
  const [total, setTotal] = useState(0);
  const [selected, setSelected] = useState<AdminActivityLog | null>(null);
  const [detailOpened, { open: openDetail, close: closeDetail }] =
    useDisclosure(false);

  const fetchLogs = useCallback(
    async (p: number) => {
      setLoading(true);
      try {
        const res = await getCampaignLogs(campaignId ?? null, p, PAGE_SIZE);
        if (res.success && res.data) {
          setLogs(res.data.items ?? []);
          setTotal(res.data.totalCount ?? 0);
        }
      } catch {
        // silent
      } finally {
        setLoading(false);
      }
    },
    [campaignId],
  );

  useEffect(() => {
    fetchLogs(page);
  }, [page, fetchLogs]);

  const totalPages = Math.ceil(total / PAGE_SIZE);

  const describe = (log: AdminActivityLog) => {
    const url = log.url || "";
    const m = log.method;
    const isPromoMap = /\/promo-code-campaigns\/[^/]+\/promo-codes/.test(url);
    if (isPromoMap && m === "POST")
      return { color: "teal", label: "Promo code attached" };
    if (isPromoMap && m === "DELETE")
      return { color: "orange", label: "Promo code detached" };
    if (m === "POST") return { color: "green", label: "Campaign created" };
    if (m === "DELETE") return { color: "red", label: "Campaign deleted" };
    if (m === "PUT" || m === "PATCH")
      return { color: "blue", label: "Campaign updated" };
    return { color: "gray", label: m };
  };

  return (
    <Stack gap="md">
      <Group justify="space-between">
        <Text fw={700} size="lg">
          Change history
        </Text>
        <Button
          size="sm"
          variant="subtle"
          onClick={() => fetchLogs(page)}
          loading={loading}
        >
          Refresh
        </Button>
      </Group>

      {loading && logs.length === 0 && (
        <Group justify="center" py="xl">
          <Loader size="sm" />
        </Group>
      )}

      {!loading && logs.length === 0 && (
        <Text size="md" c="dimmed" ta="center" py="xl">
          No changes recorded yet.
        </Text>
      )}

      {logs.length > 0 && (
        <ScrollArea>
          <Timeline active={-1} bulletSize={22} lineWidth={2}>
            {logs.map((log) => {
              const { color, label } = describe(log);
              return (
                <Timeline.Item
                  key={log.id}
                  bullet={
                    <Box
                      style={{
                        width: 22,
                        height: 22,
                        borderRadius: "50%",
                        background: `var(--mantine-color-${color}-1)`,
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                      }}
                    >
                      <IconHistory
                        size={12}
                        color={`var(--mantine-color-${color}-6)`}
                      />
                    </Box>
                  }
                  title={
                    <Badge color={color} variant="light" size="sm">
                      {label}
                    </Badge>
                  }
                >
                  <Text size="xs" c="dimmed" mt={2}>
                    {moment(log.performed_at).fromNow()} · {log.admin_email}
                  </Text>
                  {(log.payload || log.before_payload || log.url) && (
                    <Button
                      size="compact-xs"
                      variant="subtle"
                      mt={4}
                      onClick={() => {
                        setSelected(log);
                        openDetail();
                      }}
                    >
                      View details
                    </Button>
                  )}
                </Timeline.Item>
              );
            })}
          </Timeline>
        </ScrollArea>
      )}

      {totalPages > 1 && (
        <Group justify="center">
          <Pagination
            total={totalPages}
            value={page}
            onChange={setPage}
            size="sm"
          />
        </Group>
      )}

      <DetailsModal
        log={selected}
        opened={detailOpened}
        onClose={closeDetail}
      />
    </Stack>
  );
}
