import type { CanonicalEvent, EventName } from "./events";

type EventHandler<K extends EventName> = (
    event: CanonicalEvent<K>
) => void | Promise<void>;

export type EventHandlerMeta = {
    componentId: string;
    componentKind: "core" | "adapter" | "extension" | "transport";
};

type EventBusLogFn = (
    level: "info" | "error",
    message: string,
    fields?: Record<string, unknown>
) => void;

type HandlerFailureEvent = {
    eventName: EventName;
    componentId: string;
    componentKind: "core" | "adapter" | "extension" | "transport";
    streamId: string | null;
    reason: "exception" | "timeout";
    error: string;
    consecutiveFailures: number;
};

export type EventBusOptions = {
    handlerTimeoutMs?: number;
    failureDegradeThreshold?: number;
    log?: EventBusLogFn;
    onHandlerFailure?: (failure: HandlerFailureEvent) => void;
};

type EventHandlerEntry<K extends EventName> = {
    handler: EventHandler<K>;
    meta: EventHandlerMeta;
};

type HandlerMap = {
    [K in EventName]: Set<EventHandlerEntry<K>>;
};

function createHandlerMap(): HandlerMap {
    return {
        system_started: new Set<EventHandlerEntry<"system_started">>(),
        platform_status: new Set<EventHandlerEntry<"platform_status">>(),
        admin_command: new Set<EventHandlerEntry<"admin_command">>(),
        chat_ingest: new Set<EventHandlerEntry<"chat_ingest">>(),
        chat_message: new Set<EventHandlerEntry<"chat_message">>(),
        chat_command: new Set<EventHandlerEntry<"chat_command">>(),
        ws_out: new Set<EventHandlerEntry<"ws_out">>()
    };
}

export class InMemoryEventBus {
    private readonly handlers: HandlerMap = createHandlerMap();
    private readonly handlerTimeoutMs: number;
    private readonly failureDegradeThreshold: number;
    private readonly log: EventBusLogFn;
    private readonly onHandlerFailure?: (failure: HandlerFailureEvent) => void;
    private readonly failureCounts = new Map<string, number>();

    public constructor(options: EventBusOptions = {}) {
        this.handlerTimeoutMs = options.handlerTimeoutMs ?? 2000;
        this.failureDegradeThreshold = options.failureDegradeThreshold ?? 10;
        this.log = options.log ?? (() => {});
        this.onHandlerFailure = options.onHandlerFailure;
    }

    public on<K extends EventName>(
        name: K,
        handler: EventHandler<K>,
        meta: EventHandlerMeta = {
            componentId: "anonymous-handler",
            componentKind: "core"
        }
    ): () => void {
        const entry: EventHandlerEntry<K> = {
            handler,
            meta
        };
        this.handlers[name].add(entry as never);
        return () => {
            this.handlers[name].delete(entry as never);
        };
    }

    public async emit<K extends EventName>(event: CanonicalEvent<K>): Promise<void> {
        for (const entry of this.handlers[event.name]) {
            await this.invokeHandler(event, entry as EventHandlerEntry<K>);
        }
    }

    private async invokeHandler<K extends EventName>(
        event: CanonicalEvent<K>,
        entry: EventHandlerEntry<K>
    ): Promise<void> {
        const failureKey = `${event.name}:${entry.meta.componentKind}:${entry.meta.componentId}`;
        const streamId = this.extractStreamId(event.payload);

        try {
            await this.withTimeout(entry.handler(event), this.handlerTimeoutMs);
            this.failureCounts.delete(failureKey);
        } catch (error) {
            const reason = error instanceof Error && error.message === "Event handler timeout."
                ? "timeout"
                : "exception";
            const message = error instanceof Error ? error.message : String(error);
            const nextFailures = (this.failureCounts.get(failureKey) ?? 0) + 1;
            this.failureCounts.set(failureKey, nextFailures);

            this.log("error", "event_handler_failure", {
                eventName: event.name,
                componentId: entry.meta.componentId,
                componentKind: entry.meta.componentKind,
                streamId,
                reason,
                error: message,
                consecutiveFailures: nextFailures
            });

            if (nextFailures >= this.failureDegradeThreshold) {
                this.onHandlerFailure?.({
                    eventName: event.name,
                    componentId: entry.meta.componentId,
                    componentKind: entry.meta.componentKind,
                    streamId,
                    reason,
                    error: message,
                    consecutiveFailures: nextFailures
                });
            }
        }
    }

    private async withTimeout<T>(task: Promise<T> | T, timeoutMs: number): Promise<T> {
        if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
            return Promise.resolve(task);
        }
        let timeout: ReturnType<typeof setTimeout> | null = null;
        try {
            return await Promise.race<T>([
                Promise.resolve(task),
                new Promise<T>((_resolveValue, rejectValue) => {
                    timeout = setTimeout(
                        () => rejectValue(new Error("Event handler timeout.")),
                        timeoutMs
                    );
                })
            ]);
        } finally {
            if (timeout) {
                clearTimeout(timeout);
            }
        }
    }

    private extractStreamId(payload: unknown): string | null {
        if (!payload || typeof payload !== "object") {
            return null;
        }
        const maybeStreamId = (payload as { streamId?: unknown }).streamId;
        return typeof maybeStreamId === "string" && maybeStreamId.trim() ? maybeStreamId : null;
    }
}
