import AppsOutlined from "@mui/icons-material/AppsOutlined";
import CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined";
import CasinoOutlined from "@mui/icons-material/CasinoOutlined";
import InsightsOutlined from "@mui/icons-material/InsightsOutlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import { useCallback, useEffect, useMemo, useState } from "react";
import { PERMISSIONS } from "@/app/permissions";
import { Button } from "@/shared/ui/Button.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { PageHead } from "@/shared/ui/PageHead.jsx";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import {
    adjustLuckyGiftPool,
    createLuckyGiftExperiment,
    DEFAULT_POOL_ID,
    endLuckyGiftExperiment,
    fetchLuckyGiftExperiments,
    fetchOpsDashboard,
    saveLuckyGiftConfig,
    setLuckyGiftExperimentOverrides,
    updateLuckyGiftExperiment,
} from "./api.js";
import { AppsView } from "./components/AppsView.jsx";
import { ConfigsView } from "./components/ConfigsView.jsx";
import { DrawsView } from "./components/DrawsView.jsx";
import { LuckyGiftConfigDialog } from "./components/LuckyGiftConfigDialog.jsx";
import { LuckyGiftExperimentManageDialog } from "./components/LuckyGiftExperimentManageDialog.jsx";
import { LuckyGiftPoolAdjustmentDialog } from "./components/LuckyGiftPoolAdjustmentDialog.jsx";
import { OpsShell } from "./components/OpsShell.jsx";
import { OverviewView } from "./components/OverviewView.jsx";
import { EXPERIMENT_ARM_LABELS, formatTrafficPercent } from "./experimentForm.js";
import { formatNumber } from "./format.js";

const views = [
    { icon: InsightsOutlined, key: "overview", label: "数据总览" },
    { icon: CardGiftcardOutlined, key: "configs", label: "幸运礼物配置" },
    { icon: CasinoOutlined, key: "draws", label: "抽奖记录" },
    { icon: AppsOutlined, key: "apps", label: "应用列表" },
];
const viewKeys = new Set(views.map((view) => view.key));

const initialDashboard = {
    apps: [],
    appSummaries: [],
    experiments: [],
    luckyGifts: [],
    poolBalances: [],
    summary: {},
};

// 实验管理权限码与 permissions.ts 常量并行开发：常量尚未生成时回退到后端约定的字面量，
// 避免部署顺序影响入口显隐；两者最终值一致（"lucky-gift:experiment"）。
const EXPERIMENT_PERMISSION = PERMISSIONS.luckyGiftExperiment || "lucky-gift:experiment";

export function initialOpsCenterView(search = globalThis.location?.search || "") {
    const requested = new URLSearchParams(search).get("view") || "";
    return viewKeys.has(requested) ? requested : "overview";
}

const denyPermission = () => false;

export function OpsCenterApp({ can = denyPermission }) {
    // 主后台旧入口通过 ?view=configs 进入独立 HTML；只接受白名单视图，异常参数仍回到总览。
    const [activeView, setActiveView] = useState(() => initialOpsCenterView());
    const [state, setState] = useState({ data: initialDashboard, error: "", loaded: false, loading: true });
    const [dialog, setDialog] = useState(null);
    const [poolDialog, setPoolDialog] = useState(null);
    const [experimentManage, setExperimentManage] = useState(null);
    const [adjustingPool, setAdjustingPool] = useState(false);
    const [savingConfig, setSavingConfig] = useState(false);
    const [experimentBusy, setExperimentBusy] = useState(false);
    const { showToast } = useToast();
    const canCreditPool = can(PERMISSIONS.luckyGiftPoolCredit);
    const canDebitPool = can(PERMISSIONS.luckyGiftPoolDebit);
    const canUpdateConfig = can(PERMISSIONS.luckyGiftUpdate);
    const canManageExperiment = can(EXPERIMENT_PERMISSION);

    const loadDashboard = useCallback(async () => {
        setState((current) => ({ ...current, error: "", loading: true }));
        try {
            const data = await fetchOpsDashboard();
            setState({ data, error: "", loaded: true, loading: false });
        } catch (error) {
            // 接口灰度期间保留已加载的数据和页面结构，单次失败只提示、不整页白屏。
            setState((current) => ({
                data: current.data || initialDashboard,
                error: error.message || "运营接口暂不可用",
                loaded: current.loaded,
                loading: false,
            }));
        }
    }, []);

    useEffect(() => {
        loadDashboard();
    }, [loadDashboard]);

    const { data } = state;

    const appOptions = useMemo(
        () =>
            data.apps.map((item) => [
                item.app_code ?? item.appCode,
                `${item.app_name ?? item.appName ?? ""} (${item.app_code ?? item.appCode})`,
            ]),
        [data.apps],
    );

    // 抽奖记录页的奖池下拉：配置和真实水位都可能先于对方存在（先发布未入账 / 老池子停用后仍有流水），取并集。
    const poolIDsByApp = useMemo(() => {
        const byApp = new Map();
        const add = (appCode, poolID) => {
            if (!appCode || !poolID) {
                return;
            }
            if (!byApp.has(appCode)) {
                byApp.set(appCode, new Set());
            }
            byApp.get(appCode).add(poolID);
        };
        data.luckyGifts.forEach((config) => add(config.app_code, config.pool_id));
        data.poolBalances.forEach((pool) => add(pool.app_code ?? pool.appCode, pool.pool_id ?? pool.poolId));
        return new Map(Array.from(byApp.entries(), ([appCode, poolIDs]) => [appCode, Array.from(poolIDs).sort()]));
    }, [data.luckyGifts, data.poolBalances]);

    const openCreateConfig = useCallback(() => {
        const firstAppCode = data.apps
            .map((item) =>
                String(item.app_code ?? item.appCode ?? "")
                    .trim()
                    .toLowerCase(),
            )
            .find(Boolean);
        if (!firstAppCode) {
            showToast("暂无可添加配置的应用", "warning");
            return;
        }
        // 金额门槛和六维上限按 App 金币口径配置，绝不能复制第一条已发布规则给另一个 App。
        // 仅传规则身份，让 configToForm 生成 disabled dynamic_v3 安全草稿；金额字段保持 0，运营补齐后才能启用。
        setDialog({
            config: {
                app_code: firstAppCode,
                enabled: false,
                is_default: true,
                pool_id: DEFAULT_POOL_ID,
                rule_version: 0,
                strategy_version: "dynamic_v3",
            },
            mode: "create",
        });
    }, [data.apps, showToast]);

    const openEditConfig = useCallback((config) => {
        setDialog({ config: structuredClone(config), mode: "edit" });
    }, []);

    const openCreateExperiment = useCallback((config) => {
        // 复用配置弹窗的实验模式：以当前行配置为实验组初始值，当前最新启用版本自动成为对照组。
        setDialog({ config: structuredClone(config), mode: "experiment" });
    }, []);

    const openPoolAdjustment = useCallback((pool, direction) => {
        if (!pool) {
            return;
        }
        // 幂等键绑定一次弹窗会话，而不是一次 fetch；网络超时或 409 后弹窗保留，后续重试不会重复入账。
        setPoolDialog({
            adjustmentId: createPoolAdjustmentID(),
            direction,
            pool: structuredClone(pool),
        });
    }, []);

    const handleSaveConfig = useCallback(
        async (config) => {
            setSavingConfig(true);
            try {
                await saveLuckyGiftConfig(config);
                showToast(`已发布 ${config.app_code} / ${config.pool_id} 的新规则版本`, "success");
                setDialog(null);
                await loadDashboard();
            } catch (error) {
                // 保存失败保持弹窗打开，运营修完参数可直接重试，不丢已填内容。
                showToast(error.message || "保存幸运礼物配置失败", "error");
            } finally {
                setSavingConfig(false);
            }
        },
        [loadDashboard, showToast],
    );

    const handleCreateExperiment = useCallback(
        async (request) => {
            setSavingConfig(true);
            try {
                const result = await createLuckyGiftExperiment(request.config.app_code, request);
                showToast(
                    `已创建 AB 实验「${request.name}」：${request.config.app_code} / ${request.config.pool_id}，实验组 v${
                        result.experiment?.treatment_rule_version || "-"
                    } 按 ${formatTrafficPercent(request.treatment_traffic_percent)} 放量`,
                    "success",
                );
                setDialog(null);
                await loadDashboard();
            } catch (error) {
                // 创建失败（含活跃实验冲突 409）保持弹窗打开，运营修完参数可直接重试，不丢已填内容。
                showToast(error.message || "创建 AB 实验失败", "error");
            } finally {
                setSavingConfig(false);
            }
        },
        [loadDashboard, showToast],
    );

    const refreshExperimentDetail = useCallback(async (experiment) => {
        // 统计（with_stats=1）由 owner 按 control/treatment 聚合，代价较高，只在管理弹窗里拉取；
        // dashboard 扇出保持轻量列表。按 pool 过滤后再按 experiment_id 精确匹配。
        const items = await fetchLuckyGiftExperiments(experiment.app_code, {
            poolId: experiment.pool_id,
            withStats: true,
        });
        return items.find((item) => item.experiment_id === experiment.experiment_id) || null;
    }, []);

    const openExperimentManage = useCallback(
        (experiment) => {
            setExperimentManage({ experiment, statsLoading: true });
            refreshExperimentDetail(experiment)
                .then((detail) => {
                    setExperimentManage((current) =>
                        current && current.experiment.experiment_id === experiment.experiment_id
                            ? { experiment: detail || current.experiment, statsLoading: false }
                            : current,
                    );
                })
                .catch((error) => {
                    // 统计加载失败不关闭弹窗：基础字段来自列表数据仍可管理，统计位展示“-”。
                    showToast(error.message || "加载实验统计失败", "error");
                    setExperimentManage((current) =>
                        current && current.experiment.experiment_id === experiment.experiment_id
                            ? { ...current, statsLoading: false }
                            : current,
                    );
                });
        },
        [refreshExperimentDetail, showToast],
    );

    const runExperimentAction = useCallback(
        async (action, successMessage) => {
            const current = experimentManage?.experiment;
            if (!current) {
                return;
            }
            setExperimentBusy(true);
            try {
                const updated = await action(current);
                showToast(successMessage(updated), "success");
                // PATCH/PUT 响应不含 arm_stats：先用响应回填状态字段让弹窗即时一致，再补拉统计；
                // 同时刷新 dashboard，让配置列表的实验徽章与发布禁用状态同步收敛。
                setExperimentManage((state) =>
                    state && state.experiment.experiment_id === current.experiment_id
                        ? { experiment: { ...state.experiment, ...updated }, statsLoading: true }
                        : state,
                );
                await loadDashboard();
                try {
                    const detail = await refreshExperimentDetail(current);
                    setExperimentManage((state) =>
                        state && state.experiment.experiment_id === current.experiment_id
                            ? { experiment: detail || state.experiment, statsLoading: false }
                            : state,
                    );
                } catch {
                    // 操作本身已成功，统计刷新失败不能再报错误 toast；保留已回填的基础字段。
                    setExperimentManage((state) => (state ? { ...state, statsLoading: false } : state));
                }
            } catch (error) {
                // 操作失败保留弹窗与输入内容，运营修正后可直接重试。
                showToast(error.message || "实验操作失败", "error");
            } finally {
                setExperimentBusy(false);
            }
        },
        [experimentManage, loadDashboard, refreshExperimentDetail, showToast],
    );

    const adjustExperimentTraffic = useCallback(
        (percent) =>
            runExperimentAction(
                (current) =>
                    updateLuckyGiftExperiment(current.app_code, current.experiment_id, {
                        treatment_traffic_percent: percent,
                    }),
                () => `已调整实验组流量为 ${formatTrafficPercent(percent)}`,
            ),
        [runExperimentAction],
    );

    const setExperimentStatus = useCallback(
        (status) =>
            runExperimentAction(
                (current) => updateLuckyGiftExperiment(current.app_code, current.experiment_id, { status }),
                () =>
                    status === "paused"
                        ? "实验已暂停，全部流量（含白名单）回到对照组"
                        : "实验已恢复，按原分组继续分流",
            ),
        [runExperimentAction],
    );

    const upsertExperimentOverride = useCallback(
        ({ arm, userKey }) =>
            runExperimentAction(
                (current) =>
                    setLuckyGiftExperimentOverrides(current.app_code, current.experiment_id, {
                        removes: [],
                        upserts: [{ arm, user_key: userKey }],
                    }),
                () => `已把用户 ${userKey} 固定到${EXPERIMENT_ARM_LABELS[arm] || arm}`,
            ),
        [runExperimentAction],
    );

    const removeExperimentOverride = useCallback(
        (userKey) =>
            runExperimentAction(
                (current) =>
                    setLuckyGiftExperimentOverrides(current.app_code, current.experiment_id, {
                        removes: [userKey],
                        upserts: [],
                    }),
                () => `已移除白名单用户 ${userKey}，该用户回到哈希分流`,
            ),
        [runExperimentAction],
    );

    const endExperiment = useCallback(
        (winnerArm) =>
            runExperimentAction(
                async (current) => {
                    const result = await endLuckyGiftExperiment(current.app_code, current.experiment_id, winnerArm);
                    return result.experiment;
                },
                (updated) =>
                    `实验已结束：${EXPERIMENT_ARM_LABELS[winnerArm]}配置已重新发布为 v${
                        updated?.final_rule_version || updated?.winner_rule_version || "-"
                    } 并全量生效`,
            ),
        [runExperimentAction],
    );

    const handleAdjustPool = useCallback(
        async (request) => {
            setAdjustingPool(true);
            try {
                const result = await adjustLuckyGiftPool(request);
                const adjustment = result.adjustment || {};
                const action = request.direction === "out" ? "扣减" : "添加";
                showToast(
                    `已${action}水位 ${request.appCode} / ${request.poolId} / ${request.strategyVersion}：${formatNumber(
                        adjustment.balance_before,
                    )} → ${formatNumber(adjustment.balance_after)}`,
                    "success",
                );
                setPoolDialog(null);
                await loadDashboard();
            } catch (error) {
                // owner 可能已经成功但响应丢失；保留表单与 adjustment_id，用户重试时由 owner 返回首次结果而不重复调整。
                showToast(error.message || "调整奖池水位失败", "error");
            } finally {
                setAdjustingPool(false);
            }
        },
        [loadDashboard, showToast],
    );

    const defaultAppCode = data.apps[0]?.app_code ?? data.apps[0]?.appCode ?? "";

    return (
        <OpsShell activeView={activeView} onViewChange={setActiveView} views={views}>
            <div className="ops-page">
                <PageHead meta="跨应用幸运礼物配置与抽奖数据面板" title="运营配置中心">
                    <span className={state.loading ? "ops-status is-loading" : "ops-status"}>
                        {state.loading ? "同步中" : "已就绪"}
                    </span>
                    <Button
                        disabled={state.loading}
                        startIcon={<RefreshOutlined fontSize="small" />}
                        onClick={loadDashboard}
                    >
                        刷新
                    </Button>
                </PageHead>
                {/* 首屏失败走 DataState 的错误重试页；已有数据时刷新失败只弹提示条，保留旧数据可用。 */}
                {state.error && state.loaded ? (
                    <div className="ops-alert" role="alert">
                        {state.error}
                    </div>
                ) : null}
                <DataState
                    error={state.loaded ? "" : state.error}
                    loading={state.loading && !state.loaded}
                    onRetry={loadDashboard}
                >
                    {activeView === "overview" ? (
                        <OverviewView
                            canCredit={canCreditPool}
                            canDebit={canDebitPool}
                            data={data}
                            disabled={adjustingPool || state.loading}
                            onCredit={(pool) => openPoolAdjustment(pool, "in")}
                            onDebit={(pool) => openPoolAdjustment(pool, "out")}
                        />
                    ) : null}
                    {activeView === "configs" ? (
                        <ConfigsView
                            apps={data.apps}
                            canCredit={canCreditPool}
                            canDebit={canDebitPool}
                            canExperiment={canManageExperiment}
                            canUpdate={canUpdateConfig}
                            disabled={adjustingPool || state.loading}
                            experiments={data.experiments}
                            luckyGifts={data.luckyGifts}
                            poolBalances={data.poolBalances}
                            saving={savingConfig}
                            onCreate={openCreateConfig}
                            onCreateExperiment={openCreateExperiment}
                            onCredit={(pool) => openPoolAdjustment(pool, "in")}
                            onDebit={(pool) => openPoolAdjustment(pool, "out")}
                            onEdit={openEditConfig}
                            onManageExperiment={openExperimentManage}
                        />
                    ) : null}
                    {activeView === "draws" ? (
                        <DrawsView apps={data.apps} defaultAppCode={defaultAppCode} poolIDsByApp={poolIDsByApp} />
                    ) : null}
                    {activeView === "apps" ? <AppsView apps={data.apps} /> : null}
                </DataState>
                {dialog ? (
                    <LuckyGiftConfigDialog
                        appOptions={appOptions}
                        config={dialog.config}
                        mode={dialog.mode}
                        saving={savingConfig}
                        onClose={() => setDialog(null)}
                        // 实验模式提交的是 {name, treatment_traffic_percent, overrides, config} 创建请求，
                        // 其余模式仍是 PUT /lucky-gifts/config 的完整配置对象。
                        onSubmit={dialog.mode === "experiment" ? handleCreateExperiment : handleSaveConfig}
                    />
                ) : null}
                {experimentManage ? (
                    <LuckyGiftExperimentManageDialog
                        busy={experimentBusy}
                        canManage={canManageExperiment}
                        experiment={experimentManage.experiment}
                        statsLoading={experimentManage.statsLoading}
                        onAdjustTraffic={adjustExperimentTraffic}
                        onClose={() => setExperimentManage(null)}
                        onEnd={endExperiment}
                        onRemoveOverride={removeExperimentOverride}
                        onSetStatus={setExperimentStatus}
                        onUpsertOverride={upsertExperimentOverride}
                    />
                ) : null}
                {poolDialog ? (
                    <LuckyGiftPoolAdjustmentDialog
                        adjustmentId={poolDialog.adjustmentId}
                        direction={poolDialog.direction}
                        pool={poolDialog.pool}
                        submitting={adjustingPool}
                        onClose={() => setPoolDialog(null)}
                        onSubmit={handleAdjustPool}
                    />
                ) : null}
            </div>
        </OpsShell>
    );
}

let fallbackAdjustmentSequence = 0;

function createPoolAdjustmentID() {
    if (typeof globalThis.crypto?.randomUUID === "function") {
        return globalThis.crypto.randomUUID();
    }
    fallbackAdjustmentSequence += 1;
    return `pool-adjust-${Date.now().toString(36)}-${fallbackAdjustmentSequence.toString(36)}`;
}
