import { get } from "../api/client.js";
import { ENDPOINTS } from "../api/endpoints.js";
import { comfyToast } from "./toast.js";
import { comfyAlert } from "./dialogs.js";
import { t } from "./i18n.js";
import { SettingsStore } from "./settings/SettingsStore.js";

export const VERSION_UPDATE_EVENT = "mjr:version-update-available";
const VERSION_UPDATE_STATE_KEY = "__MJR_VERSION_UPDATE_STATE__";
const LATEST_RELEASE_URL =
    "https://api.github.com/repos/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/latest";
const NIGHTLY_RELEASE_URL =
    "https://api.github.com/repos/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/tags/nightly";
const LAST_CHECK_KEY = "majoor_last_update_check";
const VERSION_TOAST_NOTICE_VERSION_KEY = "majoor_version_toast_notice_version";
const DB_RESET_NOTICE_VERSION_KEY = "majoor_db_reset_notice_version";
const _AI_FEATURES_NOTICE_LOCAL_BUILD_KEY = "majoor_ai_features_notice_local_build";
const NIGHTLY_RELEASE_MARKER_KEY = "majoor_nightly_release_marker";
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;

let _versionUpdateState: Record<string, any> = { available: false, timestamp: Date.now() };

function normalizeVersion(value: any): string {
    if (!value) return "";
    return String(value).trim().replace(/^v/i, "");
}

/**
 * Check whether a version/branch should be treated as nightly/dev build.
 */
function isNightly(version: string, branch: any): boolean {
    const v = String(version || "")
        .trim()
        .toLowerCase();
    const b = String(branch || "")
        .trim()
        .toLowerCase();
    const nightlyKeywords = ["nightly", "dev", "alpha", "experimental"];
    const hasNightlyKeyword = nightlyKeywords.some((kw) => v.includes(kw) || b.includes(kw));

    // Examples: 1.2.0+a1b2c3d, 6432abc, deadbeef...
    const hasCommitHash = v.includes("+") || (v.length > 10 && /^[a-f0-9]+$/i.test(v));

    return hasNightlyKeyword || hasCommitHash;
}

function parseVersionSegments(value: any): number[] {
    return String(value || "")
        .split("-")[0]
        .split(".")
        .map((part) => {
            const num = parseInt(part, 10);
            return Number.isFinite(num) ? Math.max(0, num) : 0;
        });
}

function isNewerVersion(remote: any, local: any): boolean {
    if (!remote || !local) return false;
    const remoteParts = parseVersionSegments(remote);
    const localParts = parseVersionSegments(local);
    const length = Math.max(remoteParts.length, localParts.length);
    for (let i = 0; i < length; i += 1) {
        const remoteValue = Number.isFinite(remoteParts[i]) ? remoteParts[i] : 0;
        const localValue = Number.isFinite(localParts[i]) ? localParts[i] : 0;
        if (remoteValue > localValue) return true;
        if (remoteValue < localValue) return false;
    }
    return false;
}

function createFetchTimeoutSignal(timeoutMs: number): { signal: AbortSignal | undefined; cleanup: () => void } {
    const ms = Math.max(1, Number(timeoutMs) || 10000);
    try {
        if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
            return { signal: AbortSignal.timeout(ms), cleanup: () => {} };
        }
    } catch (e) {
        console.debug?.(e);
    }
    try {
        if (typeof AbortController !== "undefined") {
            const controller = new AbortController();
            const timer = setTimeout(() => {
                try {
                    controller.abort();
                } catch (e) {
                    console.debug?.(e);
                }
            }, ms);
            return {
                signal: controller.signal,
                cleanup: () => {
                    try {
                        clearTimeout(timer);
                    } catch (e) {
                        console.debug?.(e);
                    }
                },
            };
        }
    } catch (e) {
        console.debug?.(e);
    }
    return { signal: undefined, cleanup: () => {} };
}

async function fetchReleasePayload(url: string): Promise<Record<string, unknown>> {
    const { signal, cleanup } = createFetchTimeoutSignal(10_000);
    const response = await fetch(url, {
        cache: "no-cache",
        signal,
        headers: {
            Accept: "application/vnd.github+json",
        },
    }).finally(() => cleanup());
    if (!response.ok) {
        throw new Error(`GitHub release request failed (${response.status})`);
    }
    const payload = await response.json().catch(() => null);
    if (!payload || typeof payload !== "object") {
        throw new Error("GitHub release returned invalid payload");
    }
    return payload;
}

async function fetchLatestReleaseVersion(): Promise<string | null> {
    const payload = await fetchReleasePayload(LATEST_RELEASE_URL);
    return normalizeVersion(payload.tag_name);
}

async function fetchNightlyReleaseMarker(): Promise<string> {
    const payload = await fetchReleasePayload(NIGHTLY_RELEASE_URL);
    const marker = String(
        payload.published_at ||
            payload.created_at ||
            payload.updated_at ||
            payload.target_commitish ||
            payload.tag_name ||
            "",
    ).trim();
    return marker;
}

function emitVersionUpdateState(state: Record<string, any>): void {
    _versionUpdateState = { ...state, timestamp: Date.now() };
    const w = typeof window !== "undefined" ? window : null;
    if (w) {
        try {
            (w as any)[VERSION_UPDATE_STATE_KEY] = _versionUpdateState;
        } catch (e) {
            console.debug?.(e);
        }
        try {
            w.dispatchEvent(new CustomEvent(VERSION_UPDATE_EVENT, { detail: _versionUpdateState }));
        } catch (e) {
            console.debug?.(e);
        }
    }
}

export function getStoredVersionUpdateState(): Record<string, any> | null {
    return _versionUpdateState;
}

function _buildLocalNoticeKey(localVersion: string, branch: any): string {
    const v = String(localVersion || "unknown")
        .trim()
        .toLowerCase();
    const b = String(branch || "unknown")
        .trim()
        .toLowerCase();
    return `${v}::${b}`;
}

export async function checkMajoorVersion({ force = false }: { force?: boolean } = {}): Promise<Record<string, any> | null | void> {
    if (typeof window === "undefined") {
        return null;
    }

    let localVersion;
    let branch;
    try {
        const result = await get(ENDPOINTS.VERSION);
        if (!result?.ok) {
            emitVersionUpdateState({ available: false });
            return null;
        }
        localVersion = normalizeVersion(result.data?.version);
        branch = String(result.data?.branch || "")
            .trim()
            .toLowerCase();
    } catch {
        emitVersionUpdateState({ available: false });
        return null;
    }
    let skipRemoteCheck = false;
    try {
        if (!force) {
            const lastCheck = Number(SettingsStore.get(LAST_CHECK_KEY) || 0);
            if (Number.isFinite(lastCheck) && Date.now() - lastCheck < CHECK_INTERVAL_MS) {
                skipRemoteCheck = true;
            }
        }
    } catch {
        // localStorage might be disabled; continue without caching
    }

    if (!localVersion || isNightly(localVersion, branch)) {
        if (skipRemoteCheck) {
            emitVersionUpdateState({ available: false });
            return null;
        }

        let nightlyMarker;
        try {
            nightlyMarker = await fetchNightlyReleaseMarker();
            const previousMarker = String(
                SettingsStore.get(NIGHTLY_RELEASE_MARKER_KEY) || "",
            ).trim();
            const updateAvailable = Boolean(
                previousMarker && nightlyMarker && previousMarker !== nightlyMarker,
            );

            emitVersionUpdateState({
                available: updateAvailable,
                current: localVersion || "nightly",
                latest: "nightly",
                channel: "nightly",
            });

            if (updateAvailable) {
                comfyToast(
                    {
                        summary: t("msg.nightlyUpdateTitle", "Nightly update available"),
                        detail: t(
                            "msg.nightlyUpdateDetail",
                            "A newer nightly build is available. Download it here: https://github.com/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/tag/nightly",
                        ),
                    },
                    "info",
                    0,
                );
            }

            if (nightlyMarker) {
                try {
                    SettingsStore.set(NIGHTLY_RELEASE_MARKER_KEY, nightlyMarker);
                } catch (e) {
                    console.debug?.(e);
                }
            }
        } catch (error) {
            console.warn("Unable to check Majoor nightly updates:", error);
            emitVersionUpdateState({ available: false });
        } finally {
            try {
                SettingsStore.set(LAST_CHECK_KEY, String(Date.now()));
            } catch {
                // ignore
            }
        }
        return null;
    }

    if (skipRemoteCheck) {
        return null;
    }

    let remoteVersion;
    try {
        remoteVersion = await fetchLatestReleaseVersion();
    } catch (error) {
        console.warn("Unable to check Majoor updates:", error);
        emitVersionUpdateState({ available: false });
        return null;
    } finally {
        try {
            SettingsStore.set(LAST_CHECK_KEY, String(Date.now()));
        } catch {
            // ignore
        }
    }

    if (!remoteVersion || !isNewerVersion(remoteVersion, localVersion)) {
        emitVersionUpdateState({ available: false });
        return null;
    }

    emitVersionUpdateState({
        available: true,
        current: localVersion,
        latest: remoteVersion,
    });

    try {
        const toastShownFor = String(
            SettingsStore.get(VERSION_TOAST_NOTICE_VERSION_KEY) || "",
        ).trim();
        if (toastShownFor !== remoteVersion) {
            comfyToast(
                {
                    summary: t("msg.newVersionTitle", "Majoor Assets Manager update available"),
                    detail: t(
                        "msg.newVersionDetail",
                        "Version {latest} is available. You are currently using {current}.",
                        {
                            latest: remoteVersion,
                            current: localVersion,
                        },
                    ),
                },
                "info",
                0,
            );
            try {
                SettingsStore.set(VERSION_TOAST_NOTICE_VERSION_KEY, remoteVersion);
            } catch (e) {
                console.debug?.(e);
            }
        }
    } catch (e) {
        console.debug?.(e);
    }

    return { current: localVersion, latest: remoteVersion };
}
