"use client";

import { useMemo, useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { FileUp } from "lucide-react";
import { StatusBadge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  IMPORT_FIELDS,
  buildImportRows,
  guessColumns,
  type ColumnMapping,
} from "@/features/cities/import-columns";
import { useI18n, useT } from "@/i18n";
import { parseCsv, type CsvTable } from "@/lib/csv";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatCurrency, formatNumber } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { importCities } from "@/services/cities";
import type { CityImportAction, CityImportField, CityImportReport } from "@/types/city";

/** As many lines as the server takes in one file. */
const MAX_LINES = 5000;

/** The select's value for "this field is not in the file": Radix refuses an empty string. */
const NONE = "none";

const ACTION_VARIANT: Record<CityImportAction, "success" | "info" | "secondary" | "destructive"> = {
  created: "success",
  updated: "info",
  unchanged: "secondary",
  failed: "destructive",
};

/**
 * Bring areas in from a CSV - the shop's cities export, or any sheet with an area name in it.
 *
 * **The operator says which column is which**: the header names give a first guess (the shop's export opens
 * already matched), and every field can be pointed at any column or at none. The cells go to the server as
 * the file has them - nothing is converted here - and the server matches each line on city + name, so the
 * same file twice changes nothing.
 *
 * **Preview, then import.** The preview is the server doing the whole import and keeping none of it, so what
 * it shows - what is added, updated, left alone, and each refused line with its reason - is exactly what the
 * import will do. Changing a column after the preview asks for a new one.
 */
export function ImportCitiesDialog({
  open,
  onOpenChange,
  currency,
}: {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  currency: string;
}) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-4xl">
        {/* Its own component, so a closed dialog forgets the file it had. */}
        <ImportForm onOpenChange={onOpenChange} currency={currency} />
      </DialogContent>
    </Dialog>
  );
}

function ImportForm({
  onOpenChange,
  currency,
}: {
  onOpenChange: (open: boolean) => void;
  currency: string;
}) {
  const t = useT();
  const { locale } = useI18n();
  const queryClient = useQueryClient();

  const [fileName, setFileName] = useState<string | null>(null);
  const [table, setTable] = useState<CsvTable | null>(null);
  const [mapping, setMapping] = useState<ColumnMapping | null>(null);
  const [report, setReport] = useState<CityImportReport | null>(null);
  const [onlyRefused, setOnlyRefused] = useState(false);

  const rows = useMemo(
    () => (table && mapping ? buildImportRows(table, mapping) : []),
    [table, mapping],
  );

  const tooMany = rows.length > MAX_LINES;
  const ready = table !== null && mapping !== null && mapping.name !== null && rows.length > 0 && !tooMany;

  const run = useMutation({
    mutationFn: (dryRun: boolean) => importCities(rows, dryRun),
    onSuccess: (response) => {
      const result = response.Model;
      if (!result) return;

      setReport(result);

      if (result.dry_run) return;

      toast.success(response.Message ?? t("cities.import.done"));
      void queryClient.invalidateQueries({ queryKey: queryKeys.cities.all });
    },
    onError: (err) => toast.error(friendlyErrorMessage(err, t)),
  });

  const readFile = async (file: File | undefined) => {
    if (!file) return;

    const parsed = parseCsv(await file.text());

    if (parsed.headers.length === 0 || parsed.rows.length === 0) {
      toast.error(t("cities.import.emptyFile"));
      return;
    }

    setFileName(file.name);
    setTable(parsed);
    setMapping(guessColumns(parsed.headers));
    setReport(null);
  };

  const choose = (field: CityImportField, value: string) => {
    if (!mapping) return;
    setMapping({ ...mapping, [field]: value === NONE ? null : Number(value) });
    // A preview of another matching says nothing about this one.
    setReport(null);
  };

  const imported = report !== null && !report.dry_run;
  const reportRows = (report?.rows ?? []).filter((row) => !onlyRefused || row.action === "failed");
  // Said once above the table rather than on every line: a store's list usually has no places at all.
  const withoutPlace = (report?.rows ?? []).filter(
    (row) => row.action !== "failed" && !row.has_location,
  ).length;

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("cities.import.title")}</DialogTitle>
        <DialogDescription>{t("cities.import.body")}</DialogDescription>
      </DialogHeader>

      <div className="space-y-5">
        {/* 1. The file */}
        <div className="flex flex-wrap items-center gap-3">
          <Label
            htmlFor="cities-import-file"
            className="inline-flex cursor-pointer items-center gap-2 rounded-md border bg-surface px-3 py-2 text-sm font-medium shadow-sm hover:bg-muted"
          >
            <FileUp className="h-4 w-4" aria-hidden />
            {fileName ? t("cities.import.otherFile") : t("cities.import.chooseFile")}
          </Label>
          <input
            id="cities-import-file"
            type="file"
            accept=".csv,text/csv"
            className="sr-only"
            disabled={run.isPending}
            onChange={(event) => {
              void readFile(event.target.files?.[0]);
              event.target.value = "";
            }}
          />
          {table && fileName ? (
            <span className="text-sm text-muted-foreground">
              {t("cities.import.fileRead", {
                name: fileName,
                count: formatNumber(table.rows.length, locale),
              })}
            </span>
          ) : null}
        </div>

        {tooMany ? (
          <p className="text-sm text-destructive">
            {t("cities.import.tooMany", { max: formatNumber(MAX_LINES, locale) })}
          </p>
        ) : null}

        {/* 2. Which column is which */}
        {table && mapping && !imported ? (
          <div className="space-y-3">
            <p className="text-sm font-medium">{t("cities.import.matchColumns")}</p>
            <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
              {IMPORT_FIELDS.map((field) => (
                <div key={field} className="space-y-1.5">
                  <Label className="text-xs">
                    {t(`cities.import.field.${field}`)}
                    {field === "name" ? <span className="text-destructive"> *</span> : null}
                  </Label>
                  <Select
                    value={mapping[field] === null ? NONE : String(mapping[field])}
                    onValueChange={(value) => choose(field, value)}
                    disabled={run.isPending}
                  >
                    <SelectTrigger aria-label={t(`cities.import.field.${field}`)}>
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value={NONE}>{t("cities.import.notInFile")}</SelectItem>
                      {table.headers.map((header, index) => (
                        <SelectItem key={index} value={String(index)}>
                          {header || t("cities.import.column", { number: formatNumber(index + 1, locale) })}
                          {table.rows[0]?.[index] ? (
                            <span className="ms-2 text-xs text-muted-foreground">
                              {t("cities.import.example", { value: table.rows[0][index].slice(0, 30) })}
                            </span>
                          ) : null}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
              ))}
            </div>
            <p className="text-xs leading-5 text-muted-foreground">{t("cities.import.matchHint")}</p>
          </div>
        ) : null}

        {/* 3. What it will do, or what it did */}
        {report ? (
          <div className="space-y-3 rounded-lg border p-4">
            <div className="flex flex-wrap items-center gap-2">
              <span className="me-1 text-sm font-medium">
                {report.dry_run ? t("cities.import.previewTitle") : t("cities.import.resultTitle")}
              </span>
              {/* "to add" on a preview, "added" once it is done */}
              <StatusBadge variant="success">
                {t(report.dry_run ? "cities.import.count.created" : "cities.import.counted.created", {
                  count: formatNumber(report.summary.created, locale),
                })}
              </StatusBadge>
              <StatusBadge variant="info">
                {t(report.dry_run ? "cities.import.count.updated" : "cities.import.counted.updated", {
                  count: formatNumber(report.summary.updated, locale),
                })}
              </StatusBadge>
              <StatusBadge variant="secondary">
                {t("cities.import.count.unchanged", { count: formatNumber(report.summary.unchanged, locale) })}
              </StatusBadge>
              <StatusBadge variant={report.summary.failed > 0 ? "destructive" : "secondary"}>
                {t("cities.import.count.failed", { count: formatNumber(report.summary.failed, locale) })}
              </StatusBadge>
            </div>

            {report.cities_created.length > 0 ? (
              <p className="text-xs leading-5 text-muted-foreground">
                {t(report.dry_run ? "cities.import.citiesToAdd" : "cities.import.citiesAdded", {
                  list: report.cities_created
                    .map((city) => `${city.name} (${formatCurrency(city.delivery_earning, currency, locale)})`)
                    .join("، "),
                })}
              </p>
            ) : null}

            {withoutPlace > 0 ? (
              <p className="text-xs leading-5 text-muted-foreground">
                {t("cities.import.withoutPlace", { count: formatNumber(withoutPlace, locale) })}
              </p>
            ) : null}

            {report.summary.failed > 0 ? (
              <label className="flex items-center gap-2 text-xs">
                <Switch checked={onlyRefused} onCheckedChange={setOnlyRefused} aria-label={t("cities.import.onlyRefused")} />
                {t("cities.import.onlyRefused")}
              </label>
            ) : null}

            <div className="max-h-80 overflow-auto rounded-md border">
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead className="w-16">{t("cities.import.line")}</TableHead>
                    <TableHead>{t("cities.import.field.governorate")}</TableHead>
                    <TableHead>{t("cities.import.field.name")}</TableHead>
                    <TableHead className="text-end">{t("cities.import.field.delivery_earning")}</TableHead>
                    <TableHead>{t("cities.import.result")}</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {reportRows.map((row) => (
                    <TableRow key={row.line}>
                      <TableCell className="numeric text-xs text-muted-foreground">
                        {formatNumber(row.line, locale)}
                      </TableCell>
                      <TableCell className="text-sm">{row.governorate ?? "—"}</TableCell>
                      <TableCell className="text-sm">{row.name ?? "—"}</TableCell>
                      <TableCell className="numeric text-end text-sm">
                        {row.delivery_earning === null
                          ? "—"
                          : formatCurrency(row.delivery_earning, currency, locale)}
                        {row.earning_source === "inherited" ? (
                          <span className="block text-xs text-muted-foreground">
                            {t("cities.source.inherited")}
                          </span>
                        ) : null}
                      </TableCell>
                      <TableCell className="text-sm">
                        {/* A preview says what would happen, the result what did. */}
                        <StatusBadge variant={ACTION_VARIANT[row.action]}>
                          {report.dry_run ? t(`cities.import.will.${row.action}`) : row.action_label}
                        </StatusBadge>
                        {row.reason ? (
                          <span className="mt-1 block text-xs text-destructive">{row.reason}</span>
                        ) : null}
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </div>
          </div>
        ) : null}
      </div>

      <DialogFooter>
        {imported ? (
          <Button onClick={() => onOpenChange(false)}>{t("common.close")}</Button>
        ) : (
          <>
            <Button variant="outline" onClick={() => onOpenChange(false)} disabled={run.isPending}>
              {t("common.cancel")}
            </Button>
            <Button
              variant="outline"
              disabled={!ready || run.isPending}
              onClick={() => run.mutate(true)}
            >
              {run.isPending && run.variables === true ? t("cities.import.previewing") : t("cities.import.preview")}
            </Button>
            {/* Only after a preview of this very matching: what is imported is what was shown. */}
            <Button
              disabled={!ready || report === null || run.isPending}
              onClick={() => run.mutate(false)}
            >
              {run.isPending && run.variables === false
                ? t("cities.import.importing")
                : t("cities.import.confirm", { count: formatNumber(rows.length, locale) })}
            </Button>
          </>
        )}
      </DialogFooter>
    </>
  );
}
