import {
  Alert,
  Badge,
  Box,
  Button,
  Group,
  List,
  Progress,
  Stack,
  Text,
  ThemeIcon,
} from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import {
  IconAlertTriangle,
  IconCheck,
  IconFileZip,
  IconUpload,
  IconX,
} from "@tabler/icons-react";
import { useState } from "react";
import { uploadDashboardVersion } from "@/lib/features/dashboard/action";
import type { UploadedVersion } from "@/lib/features/dashboard/types";
import classes from "./dashboard.module.css";

/** Mirrors the backend's BUNDLE_CONTRACT so the rules are visible before upload. */
const CONTRACT = {
  maxMb: 50,
  maxFiles: 300,
  extensions:
    "html, css, js, json, png, jpg, jpeg, gif, svg, webp, woff, woff2, ttf, otf, ico",
};

/**
 * Served straight from public/, so it needs no route, no auth and no API call.
 * Regenerate from docs/dashboard-bundle/dev-guide.html when the contract changes.
 */
const DEV_GUIDE_HREF = "/docs/dashboard-bundle-guide.pdf";

type Props = {
  dashboardId: string;
  onUploaded: (result: UploadedVersion) => void;
  /** Copy differs between "first upload" and "new version of an existing one". */
  mode?: "create" | "newVersion";
};

const BundleDropzone: React.FC<Props> = ({ dashboardId, onUploaded, mode = "create" }) => {
  const [busy, setBusy] = useState(false);
  const [progress, setProgress] = useState(0);
  const [errors, setErrors] = useState<string[]>([]);
  const [message, setMessage] = useState<string | null>(null);
  const [fileName, setFileName] = useState<string | null>(null);

  const handleDrop = async (files: File[]) => {
    const file = files[0];
    if (!file) return;

    // A failed attempt must never cost the operator their wizard progress —
    // only this panel's state resets.
    setErrors([]);
    setMessage(null);
    setFileName(file.name);
    setBusy(true);
    setProgress(0);

    const res = await uploadDashboardVersion(dashboardId, file, setProgress);
    setBusy(false);

    if (!res.success) {
      // The 422 path: every Bundle Contract violation, listed.
      setErrors(res.errors?.length ? res.errors : []);
      setMessage(res.message ?? "Upload failed.");
      return;
    }
    onUploaded(res.data as UploadedVersion);
  };

  return (
    <Stack gap="sm">
      <Dropzone
        onDrop={handleDrop}
        loading={busy}
        multiple={false}
        maxSize={CONTRACT.maxMb * 1024 * 1024}
        accept={{
          "application/zip": [".zip"],
          "application/x-zip-compressed": [".zip"],
        }}
        className={classes.dropzone}
        radius="md"
      >
        <Stack align="center" gap={6} py="xl" style={{ pointerEvents: "none" }}>
          <Dropzone.Accept>
            <ThemeIcon size={44} radius="xl" variant="light" color="teal">
              <IconUpload size={22} />
            </ThemeIcon>
          </Dropzone.Accept>
          <Dropzone.Reject>
            <ThemeIcon size={44} radius="xl" variant="light" color="red">
              <IconX size={22} />
            </ThemeIcon>
          </Dropzone.Reject>
          <Dropzone.Idle>
            <ThemeIcon size={44} radius="xl" variant="light" color="gray">
              <IconFileZip size={22} />
            </ThemeIcon>
          </Dropzone.Idle>

          <Text fw={600} size="sm">
            {mode === "create"
              ? "Drop your dashboard bundle here"
              : "Drop the new version's bundle here"}
          </Text>
          <Text size="xs" c="dimmed">
            A single <b>.zip</b>, or click to browse
          </Text>
        </Stack>
      </Dropzone>

      {busy && (
        <Box>
          <Group justify="space-between" mb={4}>
            <Text size="xs" c="dimmed">
              Uploading {fileName}
            </Text>
            <Text size="xs" c="dimmed">
              {progress}%
            </Text>
          </Group>
          <Progress value={progress} radius="xl" size="sm" animated />
        </Box>
      )}

      {/* The contract, stated before they upload rather than after it fails. */}
      <Alert variant="light" color="gray" radius="md" p="sm">
        <Stack gap={4}>
          <Group gap={6}>
            <Badge size="xs" variant="light" color="gray">
              Max {CONTRACT.maxMb} MB unzipped
            </Badge>
            <Badge size="xs" variant="light" color="gray">
              Max {CONTRACT.maxFiles} files
            </Badge>
            <Badge size="xs" variant="light" color="gray">
              Self-contained
            </Badge>
          </Group>
          <Text size="xs" c="dimmed">
            Allowed file types: {CONTRACT.extensions}. Needs a{" "}
            <b>manifest.json</b> with an <b>entry</b>, or an <b>index.html</b> at
            the root.{" "}
            <Text
              component="a"
              href={DEV_GUIDE_HREF}
              target="_blank"
              rel="noopener noreferrer"
              size="xs"
              td="underline"
            >
              Read the bundle guide (PDF)
            </Text>
            .
          </Text>
        </Stack>
      </Alert>

      {(errors.length > 0 || message) && (
        <Alert
          variant="light"
          color="red"
          radius="md"
          icon={<IconAlertTriangle size={16} />}
          title={
            errors.length > 0
              ? (message ?? "This bundle doesn't meet the contract")
              : (message ?? "Upload failed")
          }
        >
          {errors.length > 0 ? (
            <Stack gap={6}>
              <List size="xs" spacing={4}>
                {errors.map((e) => (
                  <List.Item key={e}>{e}</List.Item>
                ))}
              </List>
              <Text size="xs" c="dimmed">
                Fix the bundle and drop it again — nothing else in this wizard is
                lost.
              </Text>
            </Stack>
          ) : (
            /*
             * No contract errors means the bundle never reached validation —
             * a server-side failure (storage misconfigured, network, 5xx).
             * Saying "fix your bundle" here sends people chasing a file that
             * is fine.
             */
            <Text size="xs">
              This upload failed before your bundle was checked, so it is
              probably not a problem with the file. Try again, and if it keeps
              happening send this message to an administrator.
            </Text>
          )}
        </Alert>
      )}
    </Stack>
  );
};

export default BundleDropzone;

/** Small success summary reused by the wizard and the management page. */
export const UploadSummary: React.FC<{ result: UploadedVersion }> = ({ result }) => (
  <Alert
    variant="light"
    color="teal"
    radius="md"
    icon={<IconCheck size={16} />}
    title={`Version ${result.version.version_number} uploaded`}
  >
    <Group gap="lg">
      <Text size="xs">{result.version.file_count} files</Text>
      <Text size="xs">
        {(Number(result.version.size_bytes) / 1024 / 1024).toFixed(2)} MB
      </Text>
      <Text size="xs">Entry: {result.version.entry_point}</Text>
    </Group>
    {result.warnings?.length > 0 && (
      <List size="xs" mt={6}>
        {result.warnings.map((w) => (
          <List.Item key={w}>{w}</List.Item>
        ))}
      </List>
    )}
  </Alert>
);

export { CONTRACT as BUNDLE_CONTRACT_UI };
