import { useCallback, useEffect, useState } from "react";
import {
    Anchor,
    Container,
    Group,
    Stack,
    Text,
    Title,
} from "@mantine/core";
import { createFileRoute, Link, redirect, useNavigate } from "@tanstack/react-router";
import { Bell, LogOut, ShieldAlert, Trash2 } from "lucide-react";
import { logout, renameDeviceSession, syncWebDeviceName } from "../features/auth/auth";
import { DesktopSettings } from "../features/desktop/DesktopSettings";
import { isDesktop, persistDesktopDeviceName } from "../net/desktop";
import {
    currentPushState,
    disablePush,
    enablePush,
    pushSupported,
    type PushState,
} from "../net/push";
import {
    listSessions, revokeSession,
    type AuthSession,
} from "../net/sessions";
import { useAppStore } from "../store";
import { t } from "../i18n";
import { formatRelative, isAsciiDeviceName } from "../utils/format";
import { Badge, Button, Callout, DynText, Panel, TextField } from "../ui/primitives";
import { StateDot } from "../ui/glyphs";
import { toast } from "../ui/feedback";

export const Route = createFileRoute("/settings")({
    component: SettingsPage,
    beforeLoad: () =>
    {
        const { user, selfDeviceName } = useAppStore.getState();
        if (!user) { throw redirect({ to: "/login", search: { dev_user: undefined } }); }
        if (!selfDeviceName) { throw redirect({ to: "/setup" }); }
    },
});

function SettingsPage()
{
    const navigate = useNavigate();
    const user = useAppStore((s) => s.user);
    const sessionScope = useAppStore((s) => s.sessionScope);
    const isGuest = sessionScope === "guest";
    const selfDeviceName = useAppStore((s) => s.selfDeviceName);
    const setSelfDeviceName = useAppStore((s) => s.setSelfDeviceName);

    const [ pendingName, setPendingName ] = useState(selfDeviceName ?? "");
    const [ renaming, setRenaming ] = useState(false);

    const trimmedName = pendingName.trim();
    const canSaveName = !!trimmedName && trimmedName !== selfDeviceName;

    const handleRename = async () =>
    {
        if (!selfDeviceName) { return; }
        if (!trimmedName) { toast.warn(t("settings.deviceName.empty")); return; }
        if (!isAsciiDeviceName(trimmedName))
        {
            toast.warn(t("settings.deviceName.asciiOnly"));
            return;
        }
        if (trimmedName === selfDeviceName)
        {
            toast.warn(t("settings.deviceName.unchanged"));
            return;
        }

        setRenaming(true);
        try
        {
            // 改名只换 label，不换身份（device_id 稳定）：先落本地名（root effect 会以新名重连
            // SSE），再以同一 device_id 重新代铸，把新 label 推给 broker、原地轮换那条会话，
            // 故统一设备列表与 presence 都显示新名、不产生重复设备行。桌面在此只更新本地名，
            // broker label 于下次登录代铸时同步。
            setSelfDeviceName(trimmedName);
            persistDesktopDeviceName(trimmedName); // 桌面：持久化到 Go 配置，跨重启存活
            syncWebDeviceName(trimmedName);        // 浏览器：持久化到服务端 session，抗 PWA 存储清除
            await renameDeviceSession();
            toast.ok(t("settings.deviceName.success"));
        }
        catch (e)
        {
            toast.error(t("settings.error.delete", {
                message: e instanceof Error ? e.message : String(e),
            }));
        }
        finally
        {
            setRenaming(false);
        }
    };

    const handleUnregisterSelf = () =>
    {
        if (!selfDeviceName) { return; }
        if (!window.confirm(t("settings.unregister.currentConfirm"))) { return; }
        // logout() 已自吊销本设备的 broker 会话（/api/auth/logout，按 device_id），故无需再按
        // 设备名 DELETE（迁移后设备路由按 device_id，旧的按名删除一律 404、且属冗余）。
        setSelfDeviceName(null);
        logout();
        navigate({ to: "/login", search: { dev_user: undefined } });
    };

    const handleSignOut = () =>
    {
        logout();
        navigate({ to: "/login", search: { dev_user: undefined } });
    };

    return (
        <Container size="sm" py="lg">
            <Stack gap="lg">
                <Group justify="space-between" align="center" wrap="nowrap">
                    <Group gap="xs" align="center" wrap="nowrap">
                        <Title order={2}>{t("settings.title")}</Title>
                        {isGuest && <Badge tone="warn">{t("settings.guest.badge")}</Badge>}
                    </Group>
                    <Anchor component={Link} to="/" size="sm">
                        {t("nav.backToHome")}
                    </Anchor>
                </Group>

                {/* 受限访客标识：说明这台设备不能管理账号 / 授权设备，要完整权限请在
                    主设备上重新登录。放在最显眼处（标题下第一块）。 */}
                {isGuest && (
                    <Callout
                        tone="warn"
                        icon={<ShieldAlert size={16} />}
                        title={t("settings.guest.title")}
                    >
                        {t("settings.guest.body")}
                    </Callout>
                )}

                <Panel title={t("settings.currentDevice.title")}>
                    <Stack gap="sm">
                        <TextField
                            label={t("settings.deviceName.field")}
                            value={pendingName}
                            onChange={(e) => setPendingName(e.currentTarget.value)}
                            onKeyDown={(e) =>
                            {
                                if (e.key === "Enter" && canSaveName)
                                {
                                    e.preventDefault();
                                    void handleRename();
                                }
                            }}
                        />
                        <Group>
                            <Button
                                variant="primary"
                                onClick={handleRename}
                                disabled={!canSaveName}
                                loading={renaming}
                            >
                                {t("settings.deviceName.save")}
                            </Button>
                        </Group>
                        <div style={{ height: 1, background: "var(--divider)" }} />
                        <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">
                            {t("settings.unregister.currentHint")}
                        </Text>
                        <Group>
                            <Button
                                variant="danger"
                                leftIcon={<Trash2 size={14} />}
                                onClick={handleUnregisterSelf}
                            >
                                {t("settings.unregister.current")}
                            </Button>
                        </Group>
                    </Stack>
                </Panel>

                {isDesktop() && <DesktopSettings />}

                {pushSupported() && <PushPanel />}

                <SessionsPanel
                    isGuest={isGuest}
                    onSignedOutSelf={handleSignOut}
                />

                <Panel title={t("settings.account.title")}>
                    <Stack gap="sm">
                        {user && (
                            <Stack gap={2}>
                                <Text size="sm" fw={500} truncate>{user.name}</Text>
                                {user.id !== user.name && (
                                    <Text size="xs" c="dimmed" truncate>{user.id}</Text>
                                )}
                            </Stack>
                        )}
                        <Group>
                            <Button
                                variant="ghost"
                                leftIcon={<LogOut size={14} />}
                                onClick={handleSignOut}
                            >
                                {t("settings.account.signOut")}
                            </Button>
                        </Group>
                    </Stack>
                </Panel>
            </Stack>
        </Container>
    );
}

// PushPanel：浏览器 / PWA 的 Web Push 开关。仅 pushSupported() 为真时渲染（桌面壳
// 与 dev 均为 false——桌面走原生通知、dev 无 SW）。订阅权威态在浏览器 pushManager，
// 挂载时用 currentPushState 读取一次。
function PushPanel()
{
    const [ state, setState ] = useState<PushState | null>(null);
    const [ busy, setBusy ] = useState(false);

    useEffect(() =>
    {
        let alive = true;
        void currentPushState().then((s) => { if (alive) { setState(s); } });
        return () => { alive = false; };
    }, []);

    const handleEnable = async () =>
    {
        setBusy(true);
        try
        {
            const s = await enablePush();
            setState(s);
            if (s.subscribed) { toast.ok(t("settings.push.enableOk")); }
            else if (s.permission === "denied") { toast.warn(t("settings.push.denied")); }
            else { toast.error(t("settings.push.failed")); }
        }
        finally { setBusy(false); }
    };

    const handleDisable = async () =>
    {
        setBusy(true);
        try
        {
            const s = await disablePush();
            setState(s);
            toast.ok(t("settings.push.disableOk"));
        }
        finally { setBusy(false); }
    };

    const subscribed = state?.subscribed ?? false;
    const denied = state?.permission === "denied";

    return (
        <Panel title={t("settings.push.title")}>
            <Stack gap="sm">
                <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.push.hint")}</Text>
                {denied && !subscribed && (
                    <Text size="sm" className="justify" data-jz-level="paragraph" style={{ color: "var(--state-error)" }}>
                        {t("settings.push.deniedHint")}
                    </Text>
                )}
                <Group>
                    {subscribed ? (
                        <Button
                            variant="ghost"
                            leftIcon={<Bell size={14} />}
                            loading={busy}
                            onClick={handleDisable}
                        >
                            {t("settings.push.disable")}
                        </Button>
                    ) : (
                        <Button
                            variant="primary"
                            leftIcon={<Bell size={14} />}
                            loading={busy}
                            disabled={denied}
                            onClick={handleEnable}
                        >
                            {t("settings.push.enable")}
                        </Button>
                    )}
                    {subscribed && (
                        <Badge tone="online">{t("settings.push.enabled")}</Badge>
                    )}
                </Group>
            </Stack>
        </Panel>
    );
}

// sortSessions 把会话排成「在线在上、未活跃（offline）在下」，组内按最近活跃时间降序
// （活跃越近越靠前）。不改变原数组，返回新数组。
function sortSessions(list: AuthSession[]): AuthSession[]
{
    return [ ...list ].sort((a, b) =>
    {
        if (a.online !== b.online) { return a.online ? -1 : 1; }
        return b.lastUsedAt - a.lastUsedAt;
    });
}

// SessionsPanel：登录会话管理——浏览器 / 扫码登录的设备会话。每条显示在线状态点 +
// 设备名 + 权限级别 Badge + 「本机」标记 + 最近活跃，并提供「登出」（真正吊销该登录
// 的访问令牌）。在线会话排在上、未活跃（offline）的排在下；离线会话仍可登出。
//
// step-up：DELETE 返回 403 step_up_required 时，把待登出的 sessionId 暂存 → 发起
// 一次 prompt=login 再认证（returnTo=/settings）→ 整页跳走 → 回来后由本组件 mount
// effect 取出暂存的 {code, verifier} + sessionId，POST /auth/stepup 记录窗口，再
// 重试那条 DELETE。step-up 窗口内（后端 5 分钟）后续登出不再 403、直接成功。
//
// 受限访客（isGuest）：无权管理登录会话、后端 GET 会 403，故根本不发该请求，直接展示
// 一句克制的说明 Callout（不出现任何加载 / 失败态）。
function SessionsPanel(props: { isGuest: boolean; onSignedOutSelf: () => void })
{
    const { isGuest, onSignedOutSelf } = props;
    const [ sessions, setSessions ] = useState<AuthSession[] | null>(null);
    const [ loadError, setLoadError ] = useState(false);
    const [ busyId, setBusyId ] = useState<string | null>(null);

    // reload 拉取并写入列表，同时把结果回传给调用方（step-up 重试路径需在 revoke
    // 前从这份列表里查出 current，不能在 revoke 之后再拉——若登出的正是本机会话，
    // 令牌已失效、那次 listSessions 会 401）。在线会话排前、未活跃的排后，组内按最近
    // 活跃时间降序。失败回 null。受限访客不会走到这里（调用方在 guest 分支直接返回）。
    const reload = useCallback(async (): Promise<AuthSession[] | null> =>
    {
        setLoadError(false);
        try
        {
            const list = sortSessions(await listSessions());
            setSessions(list);
            return list;
        }
        catch { setLoadError(true); setSessions(null); return null; }
    }, []);

    // doRevoke 真正执行一条登出。current（本机）会话登出后清本地 auth、回登录页
    // （等价退出登录）；其余刷新列表。403 step_up_required 由调用方分流，不入此处。
    const finishRevoke = useCallback((sess: AuthSession) =>
    {
        if (sess.current) { onSignedOutSelf(); return; }
        toast.ok(t("settings.sessions.revoked"));
        void reload();
    }, [ onSignedOutSelf, reload ]);

    // 进页面拉一次列表。受限访客无权管理会话、后端 GET 会 403，故跳过、走说明态。
    useEffect(() =>
    {
        if (isGuest) { return; }
        void reload();
    }, [ isGuest, reload ]);

    // handleRevoke 登出一条会话（= 一台设备）。后端连带 broker 吊销 + 删设备行。
    // step-up 二次认证已移除（full 档即可信）。
    const handleRevoke = async (sess: AuthSession) =>
    {
        const confirmMsg = sess.current
            ? t("settings.sessions.confirmCurrent")
            : t("settings.sessions.confirmOther", { name: sess.deviceName });
        if (!window.confirm(confirmMsg)) { return; }

        setBusyId(sess.id);
        try
        {
            await revokeSession(sess.id);
            finishRevoke(sess);
        }
        catch (e)
        {
            toast.error(t("settings.error.delete", {
                message: e instanceof Error ? e.message : String(e),
            }));
        }
        finally { setBusyId(null); }
    };

    // 受限访客：不展示列表 / 加载 / 失败态，只给一句克制的说明——管理登录会话需在主设备
    // 上完整登录。后端 GET 本就 403，前面 effect 也已跳过请求，这里纯文案分支。
    if (isGuest)
    {
        return (
            <Panel title={t("settings.sessions.title")}>
                <Callout tone="warn" icon={<ShieldAlert size={16} />}>
                    {t("settings.sessions.guestNote")}
                </Callout>
            </Panel>
        );
    }

    return (
        <Panel title={t("settings.sessions.title")}>
            <Stack gap="sm">
                <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.sessions.hint")}</Text>
                {loadError ? (
                    <Group>
                        <Text size="sm" style={{ color: "var(--state-error)" }}>
                            {t("settings.sessions.loadError")}
                        </Text>
                        <Button size="sm" variant="ghost" onClick={() => void reload()}>
                            {t("settings.sessions.retry")}
                        </Button>
                    </Group>
                ) : sessions === null ? (
                    // 仍在加载（首屏闪一帧）：不渲染占位文案，避免「暂无」误闪。
                    null
                ) : sessions.length === 0 ? (
                    <Text size="sm" c="dimmed">{t("settings.sessions.empty")}</Text>
                ) : (
                    <Stack gap="xs">
                        {sessions.map((sess) => (
                            <SessionRow
                                key={sess.id}
                                session={sess}
                                busy={busyId === sess.id}
                                // 在线、离线会话都可登出（离线亦走 step-up，流程一致）。
                                onRevoke={() => handleRevoke(sess)}
                            />
                        ))}
                    </Stack>
                )}
            </Stack>
        </Panel>
    );
}

// 会话种类标签：迁移后每条会话即一台设备，统一复用设备类型标签
// （「网页」/「macOS 客户端」等）。
function sessionKindLabel(kind: AuthSession["kind"]): string
{
    return t(`deviceType.${kind}` as const);
}

function SessionRow(props: { session: AuthSession; busy: boolean; onRevoke?: () => void })
{
    const { session, busy, onRevoke } = props;
    const lastUsedText = session.lastUsedAt
        ? t("settings.sessions.lastUsed", { time: formatRelative(session.lastUsedAt) })
        : t("settings.sessions.neverUsed");
    const kindLabel = sessionKindLabel(session.kind);
    const onlineLabel = t(session.online ? "settings.online" : "settings.offline");
    return (
        <Panel variant="sunken" padding="tight">
            <Group justify="space-between" wrap="nowrap" align="center">
                <Stack gap={2} style={{ minWidth: 0, flex: 1 }}>
                    <Group gap={6} wrap="nowrap">
                        <StateDot
                            tone={session.online ? "online" : "offline"}
                            title={onlineLabel}
                        />
                        <Text fw={500} size="sm" truncate>
                            {session.deviceName || kindLabel}
                        </Text>
                        <Badge tone={session.scope === "guest" ? "warn" : "accent"}>
                            {session.scope === "guest"
                                ? t("settings.sessions.scopeGuest")
                                : t("settings.sessions.scopeFull")}
                        </Badge>
                        {session.current && (
                            <Badge tone="online">{t("settings.sessions.current")}</Badge>
                        )}
                    </Group>
                    <Text size="xs" c="dimmed" truncate>
                        <DynText>{onlineLabel}</DynText> · {kindLabel} · <DynText>{lastUsedText}</DynText>
                    </Text>
                </Stack>
                {onRevoke && (
                    <Button
                        size="sm"
                        variant="ghost"
                        loading={busy}
                        leftIcon={<LogOut size={13} />}
                        onClick={onRevoke}
                        style={{ color: "var(--state-error)" }}
                    >
                        {busy
                            ? t("settings.sessions.signingOut")
                            : session.current
                                ? t("settings.sessions.signOutCurrent")
                                : t("settings.sessions.signOut")}
                    </Button>
                )}
            </Group>
        </Panel>
    );
}

