"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";

import {
  deleteOddsVersion,
  getAdminPlayTypes,
  getAllConfigVersions,
  getOddsVersion,
  getOddsVersions,
  postOddsVersion,
  publishOddsVersion,
  putOddsItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { ConfigChip, ConfigChipGroup } from "@/modules/config/config-chip-group";
import { ConfigContextBanner, ConfigContextEmphasis } from "@/modules/config/config-context-banner";
import { ConfigDocPage, ConfigDocToolbar } from "@/modules/config/config-doc-page";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ConfigReadonlyValue } from "@/modules/config/config-readonly-value";
import { ConfigVersionActions } from "@/modules/config/config-version-actions";
import { ConfigVersionSwitcher } from "@/modules/config/config-version-switcher";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { resolveAdminPlayTypeDisplayName } from "@/lib/admin-play-types";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_ODDS_MANAGE, PRD_REBATE_MANAGE } from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
  AdminPlayTypeRow,
  ConfigVersionSummary,
  OddsItemRow,
  OddsVersionDetail,
} from "@/types/api/admin-config";

import {
  PRIZE_SCOPE_MULTIPLIER_HINT,
  PRIZE_SCOPE_ORDER,
  prizeScopeLabel,
  type PrizeScopeCode,
} from "@/modules/config/doc/prize-scopes";

type CatTab = "all" | "d4" | "d3" | "d2";

function oddsMultiplierLabel(oddsValue: number): string {
  return (oddsValue / 10000).toFixed(4);
}

function filterTypes(tab: CatTab, types: AdminPlayTypeRow[]): AdminPlayTypeRow[] {
  if (tab === "all") {
    return types;
  }
  const dim = tab === "d4" ? 4 : tab === "d3" ? 3 : 2;
  return types.filter((t) => t.dimension === dim);
}

type OddsConfigDocScreenProps = {
  /** 嵌入「赔率与回水」合并页时去掉外层 ConfigDocPage */
  embedded?: boolean;
};

export function OddsConfigDocScreen({ embedded = false }: OddsConfigDocScreenProps) {
  const { t, i18n } = useTranslation(["config", "adminUsers", "common"]);
  const profile = useAdminProfile();
  const canManage = adminHasAnyPermission(profile?.permissions, [PRD_ODDS_MANAGE, PRD_REBATE_MANAGE]);
  const formatDt = useAdminDateTimeFormatter();
  const [types, setTypes] = useState<AdminPlayTypeRow[]>([]);
  const [list, setList] = useState<ConfigVersionSummary[]>([]);
  const [selectedId, setSelectedId] = useState("");
  const [detail, setDetail] = useState<OddsVersionDetail | null>(null);
  const [draftRows, setDraftRows] = useState<OddsItemRow[]>([]);
  const [loadingTypes, setLoadingTypes] = useState(true);
  const [loadingList, setLoadingList] = useState(true);
  const [loadingDetail, setLoadingDetail] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const [catTab, setCatTab] = useState<CatTab>("all");
  /** User-selected play type. Empty means none selected yet and falls back to the first item in the category. */
  const [playCode, setPlayCode] = useState<string>("");

  const [rollbackOpen, setRollbackOpen] = useState(false);
  const [rollbackTarget, setRollbackTarget] = useState<ConfigVersionSummary | null>(null);
  const [publishConfirmOpen, setPublishConfirmOpen] = useState(false);
  const [activeCompareRows, setActiveCompareRows] = useState<OddsItemRow[]>([]);

  const refreshTypes = useCallback(async () => {
    setLoadingTypes(true);
    try {
      const d = await getAdminPlayTypes();
      setTypes(d.items);
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
      setTypes([]);
    } finally {
      setLoadingTypes(false);
    }
  }, [t]);

  const refreshList = useCallback(async () => {
    setLoadingList(true);
    setError(null);
    try {
      const d = await getAllConfigVersions(getOddsVersions);
      setList(d.items);
    } catch (e) {
      const msg = e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" });
      setError(msg);
      setList([]);
    } finally {
      setLoadingList(false);
    }
  }, [t]);

  useEffect(() => {
    queueMicrotask(() => {
      void refreshTypes();
      void refreshList();
    });
  }, [refreshTypes, refreshList]);

  const loadDetail = useCallback(async (id: number) => {
    setLoadingDetail(true);
    try {
      const d = await getOddsVersion(id);
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
      setDetail(null);
      setDraftRows([]);
    } finally {
      setLoadingDetail(false);
    }
  }, [t]);

  useEffect(() => {
    if (list.length === 0 || selectedId !== "") {
      return;
    }
    queueMicrotask(() => {
      const drafts = list.filter((x) => x.status === "draft").sort((a, b) => b.id - a.id);
      const active = list.find((x) => x.status === "active");
      const pick = drafts[0] ?? active ?? [...list].sort((a, b) => b.id - a.id)[0];
      if (pick) {
        setSelectedId(String(pick.id));
      }
    });
  }, [list, selectedId]);

  useEffect(() => {
    if (selectedId === "") {
      return;
    }
    const id = Number(selectedId);
    if (!Number.isFinite(id)) {
      return;
    }
    queueMicrotask(() => {
      void loadDetail(id);
    });
  }, [selectedId, loadDetail]);

  const sortedTypes = useMemo(
    () => [...types].sort((a, b) => a.sort_order - b.sort_order || a.play_code.localeCompare(b.play_code)),
    [types],
  );

  const filteredTypes = useMemo(() => filterTypes(catTab, sortedTypes), [catTab, sortedTypes]);

  const resolvedPlayCode = useMemo(() => {
    if (filteredTypes.length === 0) {
      return "";
    }
    if (playCode && filteredTypes.some((t) => t.play_code === playCode)) {
      return playCode;
    }
    return filteredTypes[0].play_code;
  }, [filteredTypes, playCode]);

  const selectedVersionSummary = useMemo(
    () => list.find((x) => String(x.id) === selectedId) ?? null,
    [list, selectedId],
  );
  const isSelectedDetail = detail !== null && String(detail.id) === selectedId;
  const selectedStatus = isSelectedDetail ? detail.status : selectedVersionSummary?.status;
  const isDraft = selectedStatus === "draft";
  const canEditDraft = isDraft && canManage;

  const scopeRows = useMemo(() => {
    const rows: Partial<Record<PrizeScopeCode, OddsItemRow>> = {};
    if (!resolvedPlayCode) {
      return rows;
    }
    for (const scope of PRIZE_SCOPE_ORDER) {
      const hit = draftRows.find((r) => r.play_code === resolvedPlayCode && r.prize_scope === scope);
      if (hit) {
        rows[scope] = hit;
      }
    }
    return rows;
  }, [draftRows, resolvedPlayCode]);

  const rebatePercentUi = useMemo(() => {
    const first = PRIZE_SCOPE_ORDER.map((s) => scopeRows[s]).find(Boolean);
    if (!first) {
      return "0";
    }
    const n = Number.parseFloat(String(first.rebate_rate));
    if (!Number.isFinite(n)) {
      return "0";
    }
    return String(Math.round(n * 10000) / 100);
  }, [scopeRows]);

  function rowIndex(play_code: string, prize_scope: string): number {
    return draftRows.findIndex((r) => r.play_code === play_code && r.prize_scope === prize_scope);
  }

  function updateOddsRow(idx: number, patch: Partial<OddsItemRow>) {
    setDraftRows((prev) => prev.map((r, i) => (i === idx ? { ...r, ...patch } : r)));
  }

  function updateOddsForScope(scope: PrizeScopeCode, patch: Partial<OddsItemRow>) {
    const idx = rowIndex(resolvedPlayCode, scope);
    if (idx >= 0) {
      updateOddsRow(idx, patch);
    }
  }

  function setRebateForPlayPercent(percentStr: string) {
    const p = Number.parseFloat(percentStr);
    const rate = Number.isFinite(p) ? p / 100 : 0;
    setDraftRows((prev) =>
      prev.map((r) =>
        r.play_code === resolvedPlayCode ? { ...r, rebate_rate: String(rate) } : r,
      ),
    );
  }

  async function handleSave() {
    if (!detail || !canEditDraft) {
      return;
    }
    setSaving(true);
    try {
      const payload = draftRows.map((r) => ({
        play_code: r.play_code,
        prize_scope: r.prize_scope,
        odds_value: r.odds_value,
        rebate_rate: Number.parseFloat(String(r.rebate_rate)) || 0,
        commission_rate: Number.parseFloat(String(r.commission_rate)) || 0,
        currency_code: r.currency_code,
        extra_config_json: r.extra_config_json,
      }));
      const d = await putOddsItems(detail.id, payload);
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
      toast.success(t("versionActions.saveDraft", { ns: "config" }));
      void refreshList();
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("wallet.saveFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function handlePublish() {
    if (!detail || !canEditDraft) {
      return;
    }
    setSaving(true);
    try {
      const d = await publishOddsVersion(detail.id);
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
      toast.success(t("versionActions.publishCurrent", { ns: "config" }));
      void refreshList();
      setSelectedId(String(d.id));
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.publishFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function requestPublishConfirm() {
    if (!detail || !canEditDraft) {
      return;
    }
    const active = list.find((x) => x.status === "active");
    if (active && active.id !== detail.id) {
      try {
        const d = await getOddsVersion(active.id);
        setActiveCompareRows(d.items);
      } catch {
        setActiveCompareRows([]);
      }
    } else {
      setActiveCompareRows([]);
    }
    setPublishConfirmOpen(true);
  }

  async function handleNewDraft() {
    setSaving(true);
    try {
      const active = list.find((x) => x.status === "active");
      const d = await postOddsVersion({
        reason: `draft ${new Date().toISOString()}`,
        clone_from_version_id: active?.id ?? null,
      });
      toast.success(t("odds.createDraftSuccess", { ns: "config", version: d.version_no }));
      await refreshList();
      setSelectedId(String(d.id));
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.createDraftFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function handleRollback() {
    if (!rollbackTarget) {
      return;
    }
    setSaving(true);
    try {
      const d = await postOddsVersion({
        reason: `rollback from v${rollbackTarget.version_no}`,
        clone_from_version_id: rollbackTarget.id,
      });
      toast.success(
        t("odds.rollbackSuccess", {
          ns: "config",
          fromVersion: rollbackTarget.version_no,
          version: d.version_no,
        }),
      );
      await refreshList();
      setSelectedId(String(d.id));
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
      setRollbackOpen(false);
      setRollbackTarget(null);
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.rollbackFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  const activeHead = list.find((x) => x.status === "active");

  async function handleDeleteVersion(row: ConfigVersionSummary) {
    try {
      await deleteOddsVersion(row.id);
      toast.success(t("versionSwitcher.delete", { ns: "config" }));
      await refreshList();
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.deleteFailed", { ns: "config" }));
      throw e;
    }
  }

  function requestRollback(row: ConfigVersionSummary) {
    setRollbackTarget(row);
    setRollbackOpen(true);
  }

  const publishDiffRows = useMemo(() => {
    if (!detail) {
      return [];
    }

    const selectedPlay = resolvedPlayCode;

    return PRIZE_SCOPE_ORDER.map((scope) => {
      const next = draftRows.find((r) => r.play_code === selectedPlay && r.prize_scope === scope);
      const old = activeCompareRows.find((r) => r.play_code === selectedPlay && r.prize_scope === scope);
      return {
        scope,
        label: prizeScopeLabel(scope, t),
        oldValue: old?.odds_value ?? null,
        newValue: next?.odds_value ?? null,
      };
    });
  }, [activeCompareRows, detail, draftRows, resolvedPlayCode, t, i18n.language]);

  const catTabs: { id: CatTab; label: string }[] = [
    { id: "all", label: t("odds.tabs.all", { ns: "config" }) },
    { id: "d4", label: "4D" },
    { id: "d3", label: "3D" },
    { id: "d2", label: "2D" },
  ];

  const filtersBlock = (
        <div className="space-y-4 rounded-xl border border-border/60 bg-card p-4">
          <ConfigChipGroup label={t("odds.category", { ns: "config" })}>
            {catTabs.map((tab) => (
              <ConfigChip
                key={tab.id}
                active={catTab === tab.id}
                onClick={() => setCatTab(tab.id)}
              >
                {tab.label}
              </ConfigChip>
            ))}
          </ConfigChipGroup>
          <ConfigChipGroup label={t("odds.playType", { ns: "config" })}>
            {filteredTypes.length === 0 ? (
              <span className="text-sm text-muted-foreground">{t("odds.noPlayTypes", { ns: "config" })}</span>
            ) : (
              filteredTypes.map((type) => (
                <ConfigChip
                  key={type.play_code}
                  active={resolvedPlayCode === type.play_code}
                  onClick={() => setPlayCode(type.play_code)}
                >
                  {resolveAdminPlayTypeDisplayName(type.play_code, i18n.language, type)}
                </ConfigChip>
              ))
            )}
          </ConfigChipGroup>
        </div>
  );

  const toolbarBlock = (
        <ConfigDocToolbar
          switcher={
            <ConfigVersionSwitcher
              versions={list}
              selectedId={selectedId}
              onSelectedIdChange={setSelectedId}
              loading={loadingList}
              sheetTitle={`${t("nav.items.odds", { ns: "config" })} ${t("versionSwitcher.sheetTitle", { ns: "config" })}`}
              sheetDescription={embedded ? undefined : t("odds.sheetDescription", { ns: "config" })}
              onDeleteVersion={handleDeleteVersion}
              onRollbackVersion={requestRollback}
              rollbackBusy={saving}
            />
          }
          actions={
            <ConfigVersionActions
              isDraft={isDraft}
              canManage={canManage}
              loadingList={loadingList}
              loadingDetail={loadingDetail}
              saving={saving}
              onRefresh={() => void refreshList()}
              onNewDraft={() => void handleNewDraft()}
              onSaveDraft={() => void handleSave()}
              onPublish={() => void requestPublishConfirm()}
            />
          }
        />
  );

  const contextBlock =
    embedded || !detail ? null : (
      <ConfigContextBanner emphasis={!isDraft}>
        {t("odds.activeVersionPrefix", { ns: "config" })}
        {activeHead ? (
          <>
            v{activeHead.version_no}
            {activeHead.effective_at ? ` · ${formatDt(activeHead.effective_at)}` : ""}
          </>
        ) : (
          "—"
        )}
        {!isDraft ? (
          <>
            {" "}
            — <ConfigContextEmphasis>{t("odds.readOnlyHint", { ns: "config" })}</ConfigContextEmphasis>
          </>
        ) : null}
      </ConfigContextBanner>
    );

  const mainBlock = (
    <>
      {error ? <p className="text-sm text-destructive">{error}</p> : null}

        {loadingDetail || loadingTypes ? (
          <div className="flex min-h-[420px] items-center">
          <p className="text-base text-muted-foreground">{t("odds.loadingDetails", { ns: "config" })}</p>
          </div>
        ) : resolvedPlayCode ? (
          <div className="grid min-h-[420px] gap-4 max-w-md">
            {PRIZE_SCOPE_ORDER.map((scope) => {
              const row = scopeRows[scope];
              const hint = embedded ? null : PRIZE_SCOPE_MULTIPLIER_HINT[scope];
              const idx = row ? rowIndex(resolvedPlayCode, scope) : -1;
              return (
                <div key={scope} className="grid gap-1">
                  <Label className="flex items-baseline gap-2">
                    {prizeScopeLabel(scope, t)}
                    {hint ? <span className="text-sm text-muted-foreground font-normal">{hint}</span> : null}
                  </Label>
                  {row && idx >= 0 ? (
                    <div className="flex flex-wrap items-center gap-2">
                      {canEditDraft ? (
                        <Input
                          type="text"
                          inputMode="numeric"
                          className="h-9 max-w-[200px] font-mono tabular-nums"
                          disabled={saving}
                          value={row.odds_value}
                          onChange={(e) =>
                            updateOddsForScope(scope, {
                              odds_value: Number.parseInt(e.target.value, 10) || 0,
                            })
                          }
                        />
                      ) : (
                        <ConfigReadonlyValue mono className="max-w-[200px]">
                          {row.odds_value}
                        </ConfigReadonlyValue>
                      )}
                      {!embedded ? (
                        <span className="text-sm text-muted-foreground tabular-nums">
                          {t("odds.multiplier", {
                            ns: "config",
                            value: oddsMultiplierLabel(row.odds_value),
                            currency: row.currency_code,
                          })}
                        </span>
                      ) : null}
                    </div>
                  ) : (
                    <p className="text-sm text-destructive">{t("odds.missingScopeRow", { ns: "config", scope })}</p>
                  )}
                </div>
              );
            })}
            <div className="grid gap-1 pt-2 border-t">
              <Label>{t("odds.rebateRate", { ns: "config" })}</Label>
              {canEditDraft ? (
                <Input
                  type="text"
                  inputMode="decimal"
                  className="h-9 max-w-[200px] font-mono tabular-nums"
                  disabled={saving}
                  value={rebatePercentUi}
                  onChange={(e) => setRebateForPlayPercent(e.target.value)}
                />
              ) : (
                <ConfigReadonlyValue mono className="max-w-[200px]">
                  {rebatePercentUi}
                </ConfigReadonlyValue>
              )}
              {!embedded ? (
                <p className="text-sm text-muted-foreground">{t("odds.rebateRateHint", { ns: "config" })}</p>
              ) : null}
            </div>
          </div>
        ) : null}
    </>
  );

  const dialogs = (
    <>
      <Dialog open={rollbackOpen} onOpenChange={setRollbackOpen}>
        <DialogContent showCloseButton className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle>{t("odds.rollbackDialog.title", { ns: "config" })}</DialogTitle>
            <DialogDescription>
              {t("odds.rollbackDialog.description", { ns: "config", version: rollbackTarget?.version_no ?? "—" })}
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button type="button" variant="outline" onClick={() => setRollbackOpen(false)}>
              {t("actions.cancel", { ns: "adminUsers" })}
            </Button>
            <Button type="button" onClick={() => void handleRollback()} disabled={!rollbackTarget || saving}>
              {t("odds.rollbackDialog.confirm", { ns: "config" })}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog open={publishConfirmOpen} onOpenChange={setPublishConfirmOpen}>
        <DialogContent showCloseButton className="sm:max-w-lg">
          <DialogHeader>
            <DialogTitle>{t("odds.publishDialog.title", { ns: "config" })}</DialogTitle>
            <DialogDescription>
              {t("odds.publishDialog.description", { ns: "config" })}
            </DialogDescription>
          </DialogHeader>
          <div className="rounded-lg border">
            <div className="grid grid-cols-3 border-b bg-muted/40 px-3 py-2 text-sm font-medium">
              <span>{t("odds.publishDialog.columns.prizeScope", { ns: "config" })}</span>
              <span className="text-right">{t("odds.publishDialog.columns.currentActive", { ns: "config" })}</span>
              <span className="text-right">{t("odds.publishDialog.columns.afterPublish", { ns: "config" })}</span>
            </div>
            {publishDiffRows.map((row) => (
              <div key={row.scope} className="grid grid-cols-3 px-3 py-2 text-sm">
                <span>{row.label}</span>
                <span className="text-right font-mono tabular-nums">
                  {row.oldValue === null ? "—" : row.oldValue}
                </span>
                <span className="text-right font-mono tabular-nums">{row.newValue ?? "—"}</span>
              </div>
            ))}
          </div>
          <DialogFooter>
            <Button type="button" variant="outline" onClick={() => setPublishConfirmOpen(false)}>
              {t("actions.cancel", { ns: "adminUsers" })}
            </Button>
            <Button
              type="button"
              disabled={saving}
              onClick={() => {
                setPublishConfirmOpen(false);
                void handlePublish();
              }}
            >
              {t("odds.publishDialog.confirm", { ns: "config" })}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );

  if (embedded) {
    return (
      <div className="space-y-6">
        {filtersBlock}
        {toolbarBlock}
        {contextBlock}
        {mainBlock}
        {dialogs}
      </div>
    );
  }

  return (
    <ConfigDocPage
      title={t("nav.items.odds", { ns: "config" })}
      filters={filtersBlock}
      toolbar={toolbarBlock}
      context={contextBlock}
    >
      {mainBlock}
      {dialogs}
    </ConfigDocPage>
  );
}
