import type { ExtensionDefinition } from "../types";

type ExtensionManifestItem = {
    id: string;
    enabled: boolean;
    loaded: boolean;
};

type YouTubeStatus = {
    adminPresenceActive?: boolean;
    state?: "paused" | "armed" | "connected" | "recovering" | "error";
    mode?: "auto" | "video";
    currentLiveChatId?: string | null;
    lastDiscoveryResult?: "idle" | "found" | "not_found" | "error";
};

type StreamStatusState = {
    adminCommandCount: number;
    lastCommand: string | null;
    youtube: {
        state: "unknown" | "paused" | "armed" | "connected" | "recovering" | "error";
        mode: "auto" | "video";
        discovery: "idle" | "found" | "not_found" | "error";
        currentLiveChatId: string | null;
    };
    twitch: {
        state:
            | "unknown"
            | "idle"
            | "connecting"
            | "connected"
            | "reconnecting"
            | "disconnected"
            | "active"
            | "error";
        lastMessageAt: string | null;
        message: string | null;
        details: Record<string, unknown>;
    };
    extensions: {
        total: number;
        active: number;
        inactive: number;
    };
};

function nowIso(): string {
    return new Date().toISOString();
}

function defaultState(): StreamStatusState {
    return {
        adminCommandCount: 0,
        lastCommand: null,
        youtube: {
            state: "unknown",
            mode: "auto",
            discovery: "idle",
            currentLiveChatId: null
        },
        twitch: {
            state: "unknown",
            lastMessageAt: null,
            message: null,
            details: {}
        },
        extensions: {
            total: 0,
            active: 0,
            inactive: 0
        }
    };
}

async function streamIds(api: Parameters<ExtensionDefinition["init"]>[0]): Promise<string[]> {
    const rows = await api.db.listStreams();
    return rows.map((row) => row.id);
}

export const systemStatusExtension: ExtensionDefinition = {
    id: "system-status",
    init(api) {
        const publishStatus = async (streamId: string): Promise<void> => {
            const state = api.getState<StreamStatusState>(streamId, "status", defaultState());
            await api.emit("ws_out", {
                streamId,
                target: "all",
                event: "system_status",
                data: {
                    updatedAt: nowIso(),
                    adminCommandCount: state.adminCommandCount,
                    lastCommand: state.lastCommand,
                    platforms: {
                        youtube: state.youtube,
                        twitch: state.twitch
                    },
                    extensions: state.extensions
                }
            });
        };

        const publishAllStreams = async (): Promise<void> => {
            const ids = await streamIds(api);
            for (const streamId of ids) {
                await publishStatus(streamId);
            }
        };

        const updateYouTubeStateFromPayload = (streamId: string, payload: YouTubeStatus): void => {
            const state = api.getState<StreamStatusState>(streamId, "status", defaultState());
            const mode = payload.mode === "video" ? "video" : "auto";
            const current = typeof payload.currentLiveChatId === "string" ? payload.currentLiveChatId : null;
            const discovery = payload.lastDiscoveryResult ?? "idle";
            const nextState = payload.state ?? (current ? "connected" : "paused");
            state.youtube = {
                state: nextState,
                mode,
                discovery,
                currentLiveChatId: current
            };
            api.setState(streamId, "status", state);
        };

        const updateTwitchStateFromPayload = (
            streamId: string,
            payload: {
                state?: string;
                message?: string;
                details?: Record<string, unknown>;
            }
        ): void => {
            const state = api.getState<StreamStatusState>(streamId, "status", defaultState());
            const nextState =
                payload.state === "connecting" ||
                payload.state === "connected" ||
                payload.state === "reconnecting" ||
                payload.state === "disconnected" ||
                payload.state === "active" ||
                payload.state === "idle" ||
                payload.state === "error"
                    ? payload.state
                    : "unknown";
            state.twitch = {
                state: nextState,
                lastMessageAt: state.twitch.lastMessageAt,
                message: typeof payload.message === "string" ? payload.message : null,
                details: payload.details && typeof payload.details === "object" ? payload.details : {}
            };
            api.setState(streamId, "status", state);
        };

        api.on("system_started", async (event) => {
            api.log("system_started_observed", {
                host: event.payload.host,
                port: event.payload.port,
                version: event.payload.version
            });
            await publishAllStreams();
        });

        api.on("admin_command", async (event) => {
            const state = api.getState<StreamStatusState>(event.payload.streamId, "status", defaultState());
            state.adminCommandCount += 1;
            state.lastCommand = event.payload.command;
            api.setState(event.payload.streamId, "status", state);
            await publishStatus(event.payload.streamId);
        });

        api.on("chat_ingest", async (event) => {
            if (event.payload.platform === "twitch") {
                const ids = await streamIds(api);
                for (const streamId of ids) {
                    const state = api.getState<StreamStatusState>(streamId, "status", defaultState());
                    state.twitch = {
                        state: "active",
                        lastMessageAt: nowIso(),
                        message: "Received Twitch chat message.",
                        details: state.twitch.details
                    };
                    api.setState(streamId, "status", state);
                }
                await publishAllStreams();
                return;
            }

            if (event.payload.platform === "youtube") {
                const ids = await streamIds(api);
                for (const streamId of ids) {
                    const state = api.getState<StreamStatusState>(streamId, "status", defaultState());
                    if (state.youtube.state !== "error") {
                        state.youtube.state = "connected";
                    }
                    api.setState(streamId, "status", state);
                }
                await publishAllStreams();
            }
        });

        api.on("platform_status", async (event) => {
            if (event.payload.platform !== "twitch") {
                return;
            }
            const ids = await streamIds(api);
            for (const streamId of ids) {
                updateTwitchStateFromPayload(streamId, {
                    state: event.payload.state,
                    message: event.payload.message,
                    details: event.payload.details
                });
            }
            await publishAllStreams();
        });

        api.on("ws_out", async (event) => {
            if (event.payload.event === "system_status") {
                return;
            }

            if (event.payload.event === "extensions_manifest") {
                const extensionsRaw =
                    (event.payload.data.extensions as ExtensionManifestItem[] | undefined) ?? [];
                const active = extensionsRaw.filter((item) => item.enabled && item.loaded).length;
                const total = extensionsRaw.length;
                const state = api.getState<StreamStatusState>(event.payload.streamId, "status", defaultState());
                state.extensions = {
                    total,
                    active,
                    inactive: Math.max(0, total - active)
                };
                api.setState(event.payload.streamId, "status", state);
                await publishStatus(event.payload.streamId);
                return;
            }

            if (event.payload.event === "youtube_ops_status") {
                updateYouTubeStateFromPayload(
                    event.payload.streamId,
                    event.payload.data as unknown as YouTubeStatus
                );
                await publishStatus(event.payload.streamId);
                return;
            }

            if (event.payload.event === "youtube_ops_result") {
                const payloadStatus =
                    (event.payload.data.status as YouTubeStatus | undefined) ??
                    (event.payload.data as unknown as YouTubeStatus);
                updateYouTubeStateFromPayload(event.payload.streamId, payloadStatus);
                await publishStatus(event.payload.streamId);
            }
        });
    }
};
