import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { FileDataStore } from "../db/fileStore";
import { discoverBuiltInExtensions } from "../extensions/builtins";
import { discoverExternalExtensions } from "../extensions/external";
import { ExtensionLoader } from "../extensions/loader";
import { InMemoryEventBus } from "../hub/eventBus";
import { createEvent } from "../hub/events";
import { HubStateRegistry } from "../hub/stateRegistry";

type WsOutRecord = {
    streamId: string;
    target: "all" | "admin" | "viewer";
    event: string;
    data: Record<string, unknown>;
};

function assert(condition: boolean, message: string): void {
    if (!condition) {
        throw new Error(message);
    }
}

async function main(): Promise<void> {
    const smokeDataFile = resolve(
        process.cwd(),
        "data",
        `phase9-smoke-${process.pid}-${Date.now()}.json`
    );
    const smokeChatDir = resolve(
        process.cwd(),
        "data",
        `phase9-chat-${process.pid}-${Date.now()}`
    );
    const expiredChatLogFile = resolve(smokeChatDir, "2000-01-01.json");
    mkdirSync(smokeChatDir, { recursive: true });
    writeFileSync(expiredChatLogFile, "[]", "utf8");
    const store = new FileDataStore(smokeDataFile, {
        chatLogDir: smokeChatDir,
        chatRetentionDays: 30
    });
    const bus = new InMemoryEventBus();
    const stateRegistry = new HubStateRegistry();
    const wsOut: WsOutRecord[] = [];

    await store.init();
    const stream = await store.createStream({ name: "phase9-smoke" });

    bus.on("ws_out", async (event) => {
        wsOut.push({
            streamId: event.payload.streamId,
            target: event.payload.target,
            event: event.payload.event,
            data: event.payload.data
        });
    });

    const loader = new ExtensionLoader(bus, stateRegistry, store, () => {});

    try {
        const builtInExtensions = await discoverBuiltInExtensions();
        const externalExtensions = await discoverExternalExtensions();
        await loader.load([...builtInExtensions, ...externalExtensions]);

        await bus.emit(
            createEvent("chat_message", {
                streamId: stream.id,
                platform: "twitch",
                author: "alice",
                message: "hello world"
            })
        );

        await bus.emit(
            createEvent("chat_command", {
                streamId: stream.id,
                command: "poll",
                args: ["start", "Should", "we", "ship", "it?"],
                source: "chat",
                origin: "admin",
                actor: {
                    role: "admin",
                    platform: "local",
                    userId: "phase9-admin-token"
                }
            })
        );
        await bus.emit(
            createEvent("chat_command", {
                streamId: stream.id,
                command: "poll",
                args: ["vote", "yes"],
                source: "chat",
                origin: "chat",
                actor: {
                    role: "viewer",
                    platform: "twitch",
                    username: "alice"
                }
            })
        );
        await bus.emit(
            createEvent("chat_command", {
                streamId: stream.id,
                command: "poll",
                args: ["status"],
                source: "chat",
                origin: "chat",
                actor: {
                    role: "viewer",
                    platform: "twitch",
                    username: "alice"
                }
            })
        );
        await bus.emit(
            createEvent("chat_command", {
                streamId: stream.id,
                command: "poll",
                args: ["end"],
                source: "chat",
                origin: "admin",
                actor: {
                    role: "admin",
                    platform: "local",
                    userId: "phase9-admin-token"
                }
            })
        );
        await bus.emit(
            createEvent("platform_status", {
                platform: "twitch",
                state: "connected",
                message: "Connected to Twitch IRC.",
                details: {
                    channel: "phase9"
                }
            })
        );

        const mergedMessages = wsOut.filter((item) => item.event === "merged_chat_message");
        const pollUpdates = wsOut.filter((item) => item.event === "poll_update");
        const systemStatusUpdates = wsOut.filter((item) => item.event === "system_status");
        const storedChat = await store.listChatMessagesByStream(stream.id);
        const storedPolls = await store.listPollsByStream(stream.id);
        const appData = JSON.parse(readFileSync(smokeDataFile, "utf8")) as {
            chat_messages?: unknown[];
        };
        const chatLogDate = new Date().toISOString().slice(0, 10);
        const chatLogFile = resolve(smokeChatDir, `${chatLogDate}.json`);
        assert(!existsSync(expiredChatLogFile), "Expected expired chat log file to be pruned.");
        assert(existsSync(chatLogFile), "Expected date-based chat log file to exist.");
        const chatLogData = JSON.parse(readFileSync(chatLogFile, "utf8")) as unknown[];

        assert(mergedMessages.length === 1, "Expected merged-chat extension to emit one message.");
        assert(
            mergedMessages[0].data.message === "hello world",
            "Expected merged-chat payload to include original chat text."
        );

        assert(pollUpdates.length === 4, "Expected four poll updates (start, vote, status, end).");
        const finalPoll = pollUpdates[pollUpdates.length - 1].data as {
            isOpen?: boolean;
            votes?: { yes?: number; no?: number };
        };
        assert(finalPoll.isOpen === false, "Expected poll to be closed after end.");
        assert(finalPoll.votes?.yes === 1, "Expected yes vote count to be 1.");
        assert(finalPoll.votes?.no === 0, "Expected no vote count to be 0.");
        assert(storedChat.length === 1, "Expected one persisted chat message.");
        assert(storedChat[0].message === "hello world", "Expected persisted chat message content.");
        const latestSystemStatus = systemStatusUpdates[systemStatusUpdates.length - 1]?.data as
            | { platforms?: { twitch?: { state?: string; message?: string } } }
            | undefined;
        assert(
            latestSystemStatus?.platforms?.twitch?.state === "connected",
            "Expected system status to publish Twitch connected state."
        );
        assert(
            latestSystemStatus?.platforms?.twitch?.message === "Connected to Twitch IRC.",
            "Expected system status to publish Twitch status message."
        );
        assert(
            Array.isArray(appData.chat_messages) && appData.chat_messages.length === 0,
            "Expected new chat messages to stay out of the main app data file."
        );
        assert(chatLogData.length === 1, "Expected date-based chat log to contain one message.");
        assert(storedPolls.length === 1, "Expected one persisted poll record.");
        assert(storedPolls[0].question === "Should we ship it?", "Expected persisted poll question.");
        assert(storedPolls[0].votesYes === 1, "Expected persisted yes vote count.");
        assert(storedPolls[0].votesNo === 0, "Expected persisted no vote count.");
        assert(storedPolls[0].isOpen === false, "Expected persisted poll to be closed.");
        assert(Boolean(storedPolls[0].closedAt), "Expected closedAt timestamp on persisted poll.");

        console.log(
            JSON.stringify(
                {
                    ok: true,
                    mergedMessages: mergedMessages.length,
                    pollUpdates: pollUpdates.length,
                    systemStatusUpdates: systemStatusUpdates.length,
                    finalPoll,
                    persisted: {
                        chatMessages: storedChat.length,
                        polls: storedPolls.length
                    }
                },
                null,
                2
            )
        );
    } finally {
        rmSync(smokeDataFile, { force: true });
        rmSync(smokeChatDir, { force: true, recursive: true });
    }
}

main().catch((error: unknown) => {
    const message = error instanceof Error ? error.message : String(error);
    console.error(message);
    process.exit(1);
});
