import { resolve } from "node:path";
import { FileDataStore } from "../db/fileStore";
import { loadEnv } from "../env";
import { issueToken, listStreamTokens, revokeToken } from "../hub/auth";
import type { Token } from "../db/types";

type CliIo = {
    out: (line: string) => void;
    err: (line: string) => void;
};

function defaultIo(): CliIo {
    return {
        out: (line) => console.log(line),
        err: (line) => console.error(line)
    };
}

function getFlag(args: string[], name: string): string | null {
    const index = args.indexOf(name);
    if (index === -1) {
        return null;
    }
    return args[index + 1] ?? null;
}

function hasFlag(args: string[], name: string): boolean {
    return args.includes(name);
}

function stripFlag(args: string[], name: string): string[] {
    return args.filter((arg) => arg !== name);
}

function printTokensHuman(io: CliIo, streamId: string, tokens: Token[]): void {
    io.out(`Stream: ${streamId}`);
    if (tokens.length === 0) {
        io.out("No tokens found.");
        return;
    }
    for (const token of tokens) {
        io.out(
            `- id=${token.id} role=${token.role} createdAt=${token.createdAt} revokedAt=${
                token.revokedAt ?? "active"
            }`
        );
    }
}

function printHelp(io: CliIo): void {
    io.out(
        [
            "Conduit CLI",
            "",
            "Commands:",
            "  stream:create --name <streamName> [--json]",
            "  token:create --stream <streamId> --role <admin|viewer> [--json]",
            "  token:list --stream <streamId> [--json]",
            "  token:rotate --stream <streamId> --role <admin|viewer> --old <tokenId> [--json]",
            "  token:revoke --id <tokenId> [--json]"
        ].join("\n")
    );
}

export async function runCli(rawArgs: string[], io: CliIo = defaultIo()): Promise<number> {
    const [command, ...restArgs] = rawArgs;

    if (!command || command === "help" || command === "--help" || command === "-h") {
        printHelp(io);
        return 0;
    }

    const json = hasFlag(restArgs, "--json");
    const args = stripFlag(restArgs, "--json");

    const env = loadEnv();
    const store = new FileDataStore(resolve(process.cwd(), env.DATA_FILE), {
        chatLogDir: resolve(process.cwd(), env.CHAT_LOG_DIR),
        chatRetentionDays: env.CHAT_RETENTION_DAYS
    });
    await store.init();

    if (command === "stream:create") {
        const name = getFlag(args, "--name");
        if (!name?.trim()) {
            io.err("Missing required flag: --name");
            return 1;
        }

        const stream = await store.createStream({ name: name.trim() });
        if (json) {
            io.out(JSON.stringify({ ok: true, stream }, null, 2));
        } else {
            io.out(`Created stream '${stream.name}'`);
            io.out(`streamId: ${stream.id}`);
        }
        return 0;
    }

    if (command === "token:create") {
        const streamId = getFlag(args, "--stream");
        const roleRaw = getFlag(args, "--role")?.toLowerCase();
        if (!streamId?.trim()) {
            io.err("Missing required flag: --stream");
            return 1;
        }
        if (roleRaw !== "admin" && roleRaw !== "viewer") {
            io.err("Missing or invalid flag: --role <admin|viewer>");
            return 1;
        }

        const issued = await issueToken(store, streamId.trim(), roleRaw, env.TOKEN_PEPPER);
        if (json) {
            io.out(
                JSON.stringify(
                    {
                        ok: true,
                        plainToken: issued.plainToken,
                        token: issued.token
                    },
                    null,
                    2
                )
            );
        } else {
            io.out(`Created ${roleRaw} token for stream ${issued.token.streamId}`);
            io.out(`tokenId: ${issued.token.id}`);
            io.out(`plainToken: ${issued.plainToken}`);
            io.out("Store this token now; it cannot be retrieved later.");
        }
        return 0;
    }

    if (command === "token:list") {
        const streamId = getFlag(args, "--stream");
        if (!streamId?.trim()) {
            io.err("Missing required flag: --stream");
            return 1;
        }

        const tokens = await listStreamTokens(store, streamId.trim());
        if (json) {
            io.out(JSON.stringify({ ok: true, streamId: streamId.trim(), tokens }, null, 2));
        } else {
            printTokensHuman(io, streamId.trim(), tokens);
        }
        return 0;
    }

    if (command === "token:rotate") {
        const streamId = getFlag(args, "--stream");
        const roleRaw = getFlag(args, "--role")?.toLowerCase();
        const oldTokenId = getFlag(args, "--old");

        if (!streamId?.trim()) {
            io.err("Missing required flag: --stream");
            return 1;
        }
        if (roleRaw !== "admin" && roleRaw !== "viewer") {
            io.err("Missing or invalid flag: --role <admin|viewer>");
            return 1;
        }
        if (!oldTokenId?.trim()) {
            io.err("Missing required flag: --old");
            return 1;
        }

        const existingTokens = await listStreamTokens(store, streamId.trim());
        const oldToken = existingTokens.find((item) => item.id === oldTokenId.trim());
        if (!oldToken) {
            io.err("Rotate failed: old token not found in stream.");
            return 1;
        }
        if (oldToken.revokedAt !== null) {
            io.err("Rotate failed: old token already revoked.");
            return 1;
        }
        if (oldToken.role !== roleRaw) {
            io.err("Rotate failed: old token role does not match requested role.");
            return 1;
        }

        const issued = await issueToken(store, streamId.trim(), roleRaw, env.TOKEN_PEPPER);
        const revoked = await revokeToken(store, oldTokenId.trim());
        if (!revoked) {
            io.err("Rotate failed: new token created but old token revoke failed.");
            io.err(`New token id: ${issued.token.id}`);
            io.err(`New plain token: ${issued.plainToken}`);
            return 1;
        }

        if (json) {
            io.out(
                JSON.stringify(
                    {
                        ok: true,
                        streamId: streamId.trim(),
                        oldTokenId: oldTokenId.trim(),
                        newToken: issued.token,
                        plainToken: issued.plainToken
                    },
                    null,
                    2
                )
            );
        } else {
            io.out(`Rotated ${roleRaw} token for stream ${streamId.trim()}`);
            io.out(`oldTokenId: ${oldTokenId.trim()} (revoked)`);
            io.out(`newTokenId: ${issued.token.id}`);
            io.out(`plainToken: ${issued.plainToken}`);
            io.out("Store this token now; it cannot be retrieved later.");
        }
        return 0;
    }

    if (command === "token:revoke") {
        const tokenId = getFlag(args, "--id");
        if (!tokenId?.trim()) {
            io.err("Missing required flag: --id");
            return 1;
        }

        const revoked = await revokeToken(store, tokenId.trim());
        if (!revoked) {
            io.err("Token revoke failed: token not found or already revoked.");
            return 1;
        }

        if (json) {
            io.out(JSON.stringify({ ok: true, tokenId: tokenId.trim(), revoked: true }, null, 2));
        } else {
            io.out(`Revoked token ${tokenId.trim()}`);
        }
        return 0;
    }

    io.err(`Unknown command: ${command}`);
    printHelp(io);
    return 1;
}
