import { EventEmitter } from "node:events";
import type { Duplex } from "node:stream";
import { InMemoryEventBus } from "../hub/eventBus";
import { startTwitchAdapter } from "../platform/twitch";

function delay(ms: number): Promise<void> {
    return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
}

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

class FakeSocket extends EventEmitter {
    public readonly writes: string[] = [];
    public destroyed = false;

    public write(chunk: string | Buffer): boolean {
        this.writes.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk);
        return true;
    }

    public destroy(): void {
        if (this.destroyed) {
            return;
        }
        this.destroyed = true;
        this.emit("close");
    }
}

async function main(): Promise<void> {
    const bus = new InMemoryEventBus();
    const seen: Array<{ platform: string; author: string; message: string }> = [];
    const statuses: Array<{ platform: string; state: string; message: string }> = [];
    const fakeSocket = new FakeSocket();

    bus.on("chat_ingest", async (event) => {
        seen.push({
            platform: event.payload.platform,
            author: event.payload.author,
            message: event.payload.message
        });
    });
    bus.on("platform_status", async (event) => {
        statuses.push({
            platform: event.payload.platform,
            state: event.payload.state,
            message: event.payload.message
        });
    });

    const stop = startTwitchAdapter({
        eventBus: bus,
        oauthToken: "oauth:test-token",
        botNick: "ConduitBot",
        channel: "mychannel",
        host: "irc.chat.twitch.tv",
        port: 6697,
        reconnectMs: 10,
        log: () => {},
        connectImpl: () => fakeSocket as unknown as Duplex
    });

    try {
        fakeSocket.emit("secureConnect");
        fakeSocket.emit("data", "PING :tmi.twitch.tv\r\n");
        fakeSocket.emit(
            "data",
            "@badge-info=;badges=;display-name=Alice;id=abc123 :alice!alice@alice.tmi.twitch.tv PRIVMSG #mychannel :hello from twitch\r\n"
        );
        fakeSocket.emit(
            "data",
            "@badge-info=;badges=;display-name=Alice;id=abc123 :alice!alice@alice.tmi.twitch.tv PRIVMSG #mychannel :duplicate id should dedupe\r\n"
        );

        await delay(25);
        stop();
        await delay(20);

        const writes = fakeSocket.writes.join("");
        assert(writes.includes("PASS oauth:test-token"), "Expected PASS command on connect.");
        assert(writes.includes("NICK conduitbot"), "Expected lowercased NICK command.");
        assert(writes.includes("JOIN #mychannel"), "Expected JOIN command.");
        assert(writes.includes("PONG :tmi.twitch.tv"), "Expected PONG reply to server PING.");

        assert(seen.length === 1, "Expected one deduplicated chat_ingest.");
        assert(seen[0].platform === "twitch", "Expected platform to be twitch.");
        assert(seen[0].author === "Alice", "Expected author mapping from display-name tag.");
        assert(seen[0].message === "hello from twitch", "Expected PRIVMSG body mapping.");
        assert(
            statuses.some((item) => item.platform === "twitch" && item.state === "connecting"),
            "Expected Twitch connecting status."
        );
        assert(
            statuses.some((item) => item.platform === "twitch" && item.state === "connected"),
            "Expected Twitch connected status."
        );
        assert(
            statuses.some((item) => item.platform === "twitch" && item.state === "disconnected"),
            "Expected Twitch disconnected status after stop."
        );

        console.log(
            JSON.stringify(
                {
                    ok: true,
                    emittedMessages: seen.length,
                    statuses: statuses.map((item) => item.state)
                },
                null,
                2
            )
        );
    } finally {
        stop();
    }
}

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