import { apiFetch } from "./api";
import { isDesktop } from "./desktop";
import { getLocale } from "../i18n";
import { useAppStore } from "../store";

// Web Push 订阅管理。仅浏览器 / PWA——桌面壳走原生通知，不在此列。
//
// 分工：「页面打开→应用内 toast，页面关闭→系统通知」的判定在服务端（Hub.SendTo
// 投递失败即推 Web Push，见后端 internal/push）。前端这里只做三件事：把本设备的
// 推送端点登记到服务端（按设备名定位收件设备）、开关订阅、登录后重新登记。

interface VapidKeyResp
{
    enabled: boolean;
    public_key: string;
}

export interface PushState
{
    supported: boolean;
    permission: NotificationPermission; // "default" | "granted" | "denied"
    subscribed: boolean;                // 浏览器已持有 pushManager 订阅
}

// pushSupported：需 SW + PushManager + Notification 三者俱全、非桌面壳、且为 prod
// 构建（SW 仅在 prod 注册，见 main.tsx；dev 下无 SW，serviceWorker.ready 永不
// resolve）。任一不满足即视为不支持，设置页隐藏入口。
export function pushSupported(): boolean
{
    return import.meta.env.PROD
        && !isDesktop()
        && typeof navigator !== "undefined"
        && "serviceWorker" in navigator
        && typeof window !== "undefined"
        && "PushManager" in window
        && "Notification" in window;
}

function permissionNow(): NotificationPermission
{
    return typeof Notification !== "undefined" ? Notification.permission : "default";
}

async function registration(): Promise<ServiceWorkerRegistration | null>
{
    if (!pushSupported()) { return null; }
    try { return await navigator.serviceWorker.ready; }
    catch { return null; }
}

async function existingSubscription(): Promise<PushSubscription | null>
{
    const reg = await registration();
    if (!reg) { return null; }
    try { return await reg.pushManager.getSubscription(); }
    catch { return null; }
}

// currentPushState：供设置页初始化展示当前订阅 / 权限状态。
export async function currentPushState(): Promise<PushState>
{
    if (!pushSupported())
    {
        return { supported: false, permission: "default", subscribed: false };
    }
    const sub = await existingSubscription();
    return { supported: true, permission: permissionNow(), subscribed: sub !== null };
}

async function fetchVapidKey(): Promise<VapidKeyResp | null>
{
    try
    {
        const r = await apiFetch("/api/push/vapid-key");
        if (!r.ok) { return null; }
        return await r.json() as VapidKeyResp;
    }
    catch { return null; }
}

// postSubscription：把浏览器订阅 + 当前 locale 登记到服务端（apiFetch 带上
// X-Device-Name，服务端按设备名归属）。服务端以 endpoint 的哈希为主键 upsert，
// 故重复登记 / 换用户 / locale 变更都幂等覆盖。
async function postSubscription(sub: PushSubscription): Promise<boolean>
{
    const json = sub.toJSON();
    if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth) { return false; }
    try
    {
        const r = await apiFetch("/api/push/subscribe", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                endpoint: json.endpoint,
                keys: { p256dh: json.keys.p256dh, auth: json.keys.auth },
                locale: getLocale(),
            }),
        });
        return r.ok;
    }
    catch { return false; }
}

async function deleteServerSubscription(endpoint: string): Promise<void>
{
    try
    {
        await apiFetch("/api/push/subscribe", {
            method: "DELETE",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ endpoint }),
        });
    }
    catch { /* ignore：服务端订阅清理失败只是体验降级 */ }
}

// enablePush：用户在设置里开启。请求通知权限 → 创建（或复用）浏览器订阅 → 登记
// 服务端。返回最终状态；权限被拒 / 服务端未配置 VAPID / 订阅失败时 subscribed=false。
export async function enablePush(): Promise<PushState>
{
    if (!pushSupported())
    {
        return { supported: false, permission: "default", subscribed: false };
    }

    let perm = permissionNow();
    if (perm === "default")
    {
        try { perm = await Notification.requestPermission(); }
        catch { perm = permissionNow(); }
    }
    useAppStore.getState().setPushPermission(perm);
    if (perm !== "granted")
    {
        return { supported: true, permission: perm, subscribed: false };
    }

    const vapid = await fetchVapidKey();
    if (!vapid?.enabled || !vapid.public_key)
    {
        return { supported: true, permission: perm, subscribed: false };
    }

    const reg = await registration();
    if (!reg) { return { supported: true, permission: perm, subscribed: false }; }

    let sub = await reg.pushManager.getSubscription();
    if (!sub)
    {
        try
        {
            sub = await reg.pushManager.subscribe({
                userVisibleOnly: true,
                applicationServerKey: urlBase64ToUint8Array(vapid.public_key),
            });
        }
        catch
        {
            return { supported: true, permission: perm, subscribed: false };
        }
    }

    const ok = await postSubscription(sub);
    useAppStore.getState().setPushSubscribed(ok);
    return { supported: true, permission: perm, subscribed: ok };
}

// disablePush：用户在设置里关闭。删服务端 + 退订浏览器，使再次登录的 resyncPush
// 不会自动恢复（浏览器无订阅即视为用户不想要）。
export async function disablePush(): Promise<PushState>
{
    const sub = await existingSubscription();
    if (sub)
    {
        await deleteServerSubscription(sub.endpoint);
        try { await sub.unsubscribe(); }
        catch { /* ignore */ }
    }
    useAppStore.getState().setPushSubscribed(false);
    return { supported: pushSupported(), permission: permissionNow(), subscribed: false };
}

// resyncPush：登录后（root effect 内）调用。若浏览器已有订阅且权限在握，把它重新
// 登记到当前用户名下——让「同一浏览器换用户登录」「endpoint 轮换」「切换 locale」
// 自愈，且无需再弹权限。无订阅则不动（用户从未开启推送）。fire-and-forget。
export async function resyncPush(): Promise<void>
{
    if (!pushSupported() || permissionNow() !== "granted") { return; }
    const sub = await existingSubscription();
    if (!sub) { return; }
    const ok = await postSubscription(sub);
    useAppStore.getState().setPushSubscribed(ok);
}

// urlBase64ToUint8Array：把 VAPID base64url 公钥转成 applicationServerKey 需要的
// 字节数组（标准 Web Push 样板）。
function urlBase64ToUint8Array(base64: string): Uint8Array<ArrayBuffer>
{
    const padding = "=".repeat((4 - (base64.length % 4)) % 4);
    const b64 = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
    const raw = atob(b64);
    // Back the view with a concrete ArrayBuffer (not ArrayBufferLike) so it
    // satisfies BufferSource for applicationServerKey under TS 5.7 strict typing.
    const out = new Uint8Array(new ArrayBuffer(raw.length));
    for (let i = 0; i < raw.length; i += 1) { out[i] = raw.charCodeAt(i); }
    return out;
}
