import { useEffect, useState } from "react";
import { Group, Stack, Text } from "@mantine/core";
import { Copy, KeyRound, Plus } from "lucide-react";
import { apiJSON } from "../../net/api";
import { t } from "../../i18n";
import { formatRelative } from "../../utils/format";
import { Badge, Button, Callout, DynText, Panel, TextField } from "../../ui/primitives";
import { toast } from "../../ui/feedback";

type ShortcutTokenView = {
    jti: string;
    label: string;
    scopes: string[];
    created_at: number;
    expires_at: number;
    last_used_at: number | null;
    revoked: boolean;
};

type IssueResp = {
    token: string;
    jti: string;
    label: string;
    scopes: string[];
    expires_at: number;
};

const listTokens = () => apiJSON<{ tokens: ShortcutTokenView[] }>("/api/shortcut");

const issueToken = (label: string) =>
    apiJSON<IssueResp>("/api/shortcut/issue", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ label }),
    });

const revokeToken = (jti: string) =>
    apiJSON<void>(`/api/shortcut/${encodeURIComponent(jti)}`, { method: "DELETE" });

// baseURL 是本服务器的对外地址，快捷指令请求剪贴板端点时用它拼 URL。桌面注入了
// loopback 代理地址，但快捷指令跑在 iPhone 上，应当用真正的对外 origin。
function baseURL(): string
{
    return window.location.origin;
}

async function copyText(value: string): Promise<void>
{
    try
    {
        await navigator.clipboard.writeText(value);
        toast.ok(t("settings.shortcut.copied"));
    }
    catch
    {
        toast.error(t("settings.shortcut.copyFailed"));
    }
}

// ShortcutTokens 管理 iOS 快捷指令的长效、仅剪贴板、可吊销令牌：列表 / 签发
// （仅展示一次）/ 吊销 + 安装指引。挂在设置页，需登录会话（用令牌本身不能管理令牌）。
export function ShortcutTokens()
{
    const [ tokens, setTokens ] = useState<ShortcutTokenView[] | null>(null);
    const [ loadError, setLoadError ] = useState(false);
    const [ label, setLabel ] = useState("");
    const [ issuing, setIssuing ] = useState(false);
    const [ revoking, setRevoking ] = useState<Set<string>>(new Set());
    const [ issued, setIssued ] = useState<IssueResp | null>(null);

    const reload = async () =>
    {
        try
        {
            const r = await listTokens();
            setTokens(r.tokens ?? []);
            setLoadError(false);
        }
        catch
        {
            // 网络波动不该让整页报错；显示重试入口即可（容错要求）。
            setLoadError(true);
        }
    };

    useEffect(() =>
    {
        void reload();
    }, []);

    const handleIssue = async () =>
    {
        setIssuing(true);
        try
        {
            const res = await issueToken(label.trim());
            setIssued(res); // 内联展示一次性令牌 + 指引
            setLabel("");
            await reload();
        }
        catch (e)
        {
            const msg = e instanceof Error ? e.message : String(e);
            if (msg.startsWith("503")) { toast.error(t("settings.shortcut.unavailable")); }
            else if (msg.startsWith("409")) { toast.error(t("settings.shortcut.tooMany")); }
            else { toast.error(t("settings.shortcut.issueError", { message: msg })); }
        }
        finally
        {
            setIssuing(false);
        }
    };

    const handleRevoke = async (token: ShortcutTokenView) =>
    {
        if (!window.confirm(t("settings.shortcut.revokeConfirm"))) { return; }

        setRevoking((prev) => new Set(prev).add(token.jti));
        try
        {
            await revokeToken(token.jti);
            toast.ok(t("settings.shortcut.revokedToast"));
            await reload();
        }
        catch (e)
        {
            toast.error(t("settings.shortcut.issueError", {
                message: e instanceof Error ? e.message : String(e),
            }));
        }
        finally
        {
            setRevoking((prev) =>
            {
                const next = new Set(prev);
                next.delete(token.jti);
                return next;
            });
        }
    };

    return (
        <Panel title={t("settings.shortcut.title")}>
            <Stack gap="sm">
                <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.shortcut.intro")}</Text>

                <Group align="flex-end" gap="sm" wrap="nowrap">
                    <div style={{ flex: 1, minWidth: 0 }}>
                        <TextField
                            label={t("settings.shortcut.labelField")}
                            placeholder={t("settings.shortcut.labelPlaceholder")}
                            value={label}
                            onChange={(e) => setLabel(e.currentTarget.value)}
                            onKeyDown={(e) =>
                            {
                                if (e.key === "Enter" && !issuing)
                                {
                                    e.preventDefault();
                                    void handleIssue();
                                }
                            }}
                        />
                    </div>
                    <Button
                        variant="primary"
                        leftIcon={<Plus size={14} />}
                        onClick={handleIssue}
                        loading={issuing}
                    >
                        {t("settings.shortcut.issue")}
                    </Button>
                </Group>

                {issued && (
                    <IssuedReveal
                        issued={issued}
                        base={baseURL()}
                        onDismiss={() => setIssued(null)}
                    />
                )}

                {loadError && (
                    <Group gap="sm">
                        <Text size="sm" c="dimmed">{t("settings.shortcut.loadError")}</Text>
                        <Button size="sm" variant="ghost" onClick={() => void reload()}>
                            {t("settings.shortcut.retry")}
                        </Button>
                    </Group>
                )}

                {tokens && tokens.length === 0 && !loadError && (
                    <Text size="sm" c="dimmed">{t("settings.shortcut.empty")}</Text>
                )}

                {tokens && tokens.length > 0 && (
                    <Stack gap="xs">
                        {tokens.map((tok) => (
                            <TokenRow
                                key={tok.jti}
                                token={tok}
                                revoking={revoking.has(tok.jti)}
                                onRevoke={() => handleRevoke(tok)}
                            />
                        ))}
                    </Stack>
                )}
            </Stack>
        </Panel>
    );
}

function TokenRow(props: { token: ShortcutTokenView; revoking: boolean; onRevoke: () => void })
{
    const { token, revoking, onRevoke } = props;
    const expired = token.expires_at * 1000 < Date.now();
    const lastUsed = token.last_used_at
        ? t("settings.shortcut.lastUsed", { time: formatRelative(token.last_used_at) })
        : t("settings.shortcut.neverUsed");

    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">
                        <Text fw={500} size="sm" truncate>{token.label}</Text>
                        {token.revoked && <Badge tone="neutral">{t("settings.shortcut.revoked")}</Badge>}
                        {!token.revoked && expired && (
                            <Badge tone="neutral">{t("settings.shortcut.expired")}</Badge>
                        )}
                    </Group>
                    <Text size="xs" c="dimmed" truncate>
                        {t("settings.shortcut.expiresAt", {
                            date: new Date(token.expires_at * 1000).toLocaleDateString(),
                        })}
                        {" · "}
                        <DynText>{lastUsed}</DynText>
                    </Text>
                </Stack>
                {!token.revoked && (
                    <Button
                        size="sm"
                        variant="ghost"
                        loading={revoking}
                        onClick={onRevoke}
                        style={{ color: "var(--state-error)" }}
                    >
                        {revoking ? t("settings.shortcut.revoking") : t("settings.shortcut.revoke")}
                    </Button>
                )}
            </Group>
        </Panel>
    );
}

// IssuedReveal 内联展示刚签发的令牌（仅此一次）+ 服务器地址 + 手动搭建指引。
// 沿用设置页一贯的「下沉面板 + Callout + Button」语汇，不引入弹窗。
function IssuedReveal(props: { issued: IssueResp; base: string; onDismiss: () => void })
{
    const { issued, base, onDismiss } = props;
    return (
        <Panel variant="sunken" padding="tight">
            <Stack gap="sm">
                <Callout
                    tone="warn"
                    icon={<KeyRound size={16} />}
                    title={t("settings.shortcut.issuedTitle")}
                >
                    {t("settings.shortcut.onceWarning")}
                </Callout>

                <Field label={t("settings.shortcut.tokenLabel")} value={issued.token} mono />
                <Field label={t("settings.shortcut.baseLabel")} value={base} />

                <Stack gap={6}>
                    <Text size="sm" fw={600}>{t("settings.shortcut.guideTitle")}</Text>
                    <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.shortcut.guideIntro")}</Text>
                    <Text size="sm" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.shortcut.guideDeviceName")}</Text>

                    <Text size="sm" fw={500}>{t("settings.shortcut.guidePullTitle")}</Text>
                    <Stack gap={2}>
                        <Text size="sm" className="justify" data-jz-level="paragraph">1. {t("settings.shortcut.guidePull1")}</Text>
                        <Text size="sm" className="justify" data-jz-level="paragraph">2. {t("settings.shortcut.guidePull2")}</Text>
                        <Text size="sm" className="justify" data-jz-level="paragraph">3. {t("settings.shortcut.guidePull3")}</Text>
                    </Stack>

                    <Text size="sm" fw={500}>{t("settings.shortcut.guidePushTitle")}</Text>
                    <Stack gap={2}>
                        <Text size="sm" className="justify" data-jz-level="paragraph">1. {t("settings.shortcut.guidePush1")}</Text>
                        <Text size="sm" className="justify" data-jz-level="paragraph">2. {t("settings.shortcut.guidePush2")}</Text>
                        <Text size="sm" className="justify" data-jz-level="paragraph">3. {t("settings.shortcut.guidePush3")}</Text>
                    </Stack>

                    <Text size="xs" c="dimmed" className="justify" data-jz-level="paragraph">{t("settings.shortcut.guideToleranceNote")}</Text>
                </Stack>

                <Group justify="flex-end">
                    <Button variant="ghost" onClick={onDismiss}>
                        {t("settings.shortcut.done")}
                    </Button>
                </Group>
            </Stack>
        </Panel>
    );
}

// Field 是一行「标签 + 单行只读值 + 复制按钮」，值用等宽字体、可断行。
function Field(props: { label: string; value: string; mono?: boolean })
{
    const { label, value, mono } = props;
    return (
        <Stack gap={4}>
            <Text size="xs" c="dimmed">{label}</Text>
            <Group gap="xs" wrap="nowrap" align="flex-start">
                <Text
                    size="sm"
                    style={{
                        flex: 1,
                        minWidth: 0,
                        wordBreak: "break-all",
                        fontFamily: mono ? "var(--font-mono)" : undefined,
                    }}
                >
                    {value}
                </Text>
                <Button
                    size="sm"
                    variant="ghost"
                    leftIcon={<Copy size={14} />}
                    onClick={() => void copyText(value)}
                >
                    {t("settings.shortcut.copy")}
                </Button>
            </Group>
        </Stack>
    );
}
