import {
    HttpTransportType,
    type HubConnection,
    HubConnectionBuilder,
    HubConnectionState,
} from "@microsoft/signalr";
import {
    type IToolLogger,
    LogMessage,
    translate,
} from "@uipath/solutionpackager-tool-core";
import type { RemoteProjectCleanup } from "./cleanup-result.js";

/** @public */
export enum CompletedStatus {
    Succeeded = 0,
    Stopped = 1,
    Failed = 2,
}

export interface PackAgentResult {
    errorCode: number;
    status: CompletedStatus;
    message?: string;
    bucketId?: number;
    /** Cleanup only: files changed per project design id, relative to each project. */
    changedFilesByProject?: Record<string, string[]>;
    /** Cleanup only: raw per-project cleanup payload, keyed by project design id. */
    unusedDependenciesByProject?: Record<string, RemoteProjectCleanup>;
}

interface ExecutorCommand<T> {
    Id: number;
    Argument1: T;
}

interface PackJobResult {
    ErrorCode: number;
    Status: number;
    Message?: string;
    BucketId?: number;
    ChangedFilesByProject?: Record<string, string[]>;
    UnusedDependenciesByProject?: Record<string, RemoteProjectCleanup>;
}

enum PackagerCommandType {
    PeerDisconnected = "PeerDisconnected",
    SetLogMessage = "SetLogMessage",
    SetExecutionStarted = "SetExecutionStarted",
    SetExecutionCompleted = "SetExecutionCompleted",
    Stop = "Stop",
}

export class PackagerHubConnection {
    private hubConnection: HubConnection | null = null;
    private completionResolve: ((result: PackAgentResult) => void) | null =
        null;
    private completionReject: ((error: Error) => void) | null = null;

    constructor(private readonly logger?: IToolLogger) {}

    async connectAsync(hubUrl: string, accessToken: string): Promise<void> {
        try {
            this.hubConnection = this.buildConnection(hubUrl, accessToken);
            this.registerHandlers();
            this.registerOnClose();
            await this.hubConnection.start();
        } catch (error) {
            // Retry with SSE/LongPolling if WebSocket fails (matches StudioWeb pattern)
            if (
                error instanceof Error &&
                error.message.toLowerCase().includes("websocket")
            ) {
                this.hubConnection = this.buildConnection(
                    hubUrl,
                    accessToken,
                    true,
                );
                this.registerHandlers();
                this.registerOnClose();
                await this.hubConnection.start();
            } else {
                throw error;
            }
        }
    }

    private buildConnection(
        hubUrl: string,
        accessToken: string,
        disableWs = false,
    ): HubConnection {
        return new HubConnectionBuilder()
            .withUrl(hubUrl, {
                accessTokenFactory: () => accessToken,
                transport: disableWs
                    ? HttpTransportType.ServerSentEvents |
                      HttpTransportType.LongPolling
                    : undefined,
            })
            .withAutomaticReconnect()
            .build();
    }

    private registerOnClose(): void {
        this.hubConnection?.onclose((error) => {
            this.logger?.info(
                `[PackagerHub] onclose fired. error=${error?.message ?? "none"}, hasReject=${!!this.completionReject}, hasResolve=${!!this.completionResolve}`,
            );
            if (this.completionReject) {
                this.completionReject(
                    error ??
                        new Error(
                            translate.t(
                                "toolWorkflowCompilerBrowser.errors.hubConnectionClosed",
                            ),
                        ),
                );
                this.completionResolve = null;
                this.completionReject = null;
            }
        });
    }

    waitForCompletionAsync(): Promise<PackAgentResult> {
        return new Promise<PackAgentResult>((resolve, reject) => {
            this.completionResolve = resolve;
            this.completionReject = reject;
        });
    }

    async disconnectAsync(): Promise<void> {
        if (
            this.hubConnection &&
            this.hubConnection.state !== HubConnectionState.Disconnected
        ) {
            await this.hubConnection.stop();
        }
        this.hubConnection = null;
    }

    private registerHandlers(): void {
        if (!this.hubConnection) return;

        this.hubConnection.on(
            PackagerCommandType.SetExecutionCompleted,
            (jsonCommand: string) => {
                this.onExecutionCompleted(jsonCommand);
            },
        );

        this.hubConnection.on(
            PackagerCommandType.SetLogMessage,
            (jsonCommand: string) => {
                this.onLogMessage(jsonCommand);
            },
        );

        this.hubConnection.on(
            PackagerCommandType.SetExecutionStarted,
            (_jsonCommand: string) => {
                this.logger?.info(
                    translate.t(
                        "toolWorkflowCompilerBrowser.info.remoteAgentStartedExecution",
                    ),
                );
            },
        );

        this.hubConnection.on(PackagerCommandType.PeerDisconnected, () => {
            this.logger?.info(
                `[PackagerHub] PeerDisconnected received. hasReject=${!!this.completionReject}`,
            );
            if (this.completionReject) {
                this.completionReject(
                    new Error(
                        translate.t(
                            "toolWorkflowCompilerBrowser.errors.packAgentDisconnected",
                        ),
                    ),
                );
                this.completionResolve = null;
                this.completionReject = null;
            }
        });
    }

    private onLogMessage(jsonCommand: string): void {
        if (!this.logger) return;

        const command: ExecutorCommand<LogMessage> = JSON.parse(jsonCommand);
        const agentLog = command.Argument1;

        const logMessage = new LogMessage(
            agentLog.message,
            agentLog.logLevel,
            {
                source: agentLog.source,
                sourceTarget: agentLog.sourceTarget,
                progress: agentLog.progress,
                code: agentLog.code,
                filePath: agentLog.filePath,
                line: agentLog.line,
                id: agentLog.id,
            },
            agentLog.timestamp,
        );
        this.logger.logMessage(logMessage);
    }

    private onExecutionCompleted(jsonCommand: string): void {
        this.logger?.info(
            `[PackagerHub] onExecutionCompleted called. hasResolve=${!!this.completionResolve}, raw=${jsonCommand}`,
        );

        if (!this.completionResolve) {
            this.logger?.warn(
                `[PackagerHub] onExecutionCompleted: completionResolve is null, ignoring result`,
            );
            return;
        }

        const command: ExecutorCommand<PackJobResult> = JSON.parse(jsonCommand);
        const jobResult = command.Argument1;

        this.logger?.info(
            `[PackagerHub] onExecutionCompleted parsed: errorCode=${jobResult.ErrorCode}, status=${jobResult.Status}, message=${jobResult.Message}, bucketId=${jobResult.BucketId}`,
        );

        this.completionResolve({
            errorCode: jobResult.ErrorCode,
            status: jobResult.Status,
            message: jobResult.Message,
            bucketId: jobResult.BucketId,
            changedFilesByProject: jobResult.ChangedFilesByProject,
            unusedDependenciesByProject: jobResult.UnusedDependenciesByProject,
        });
        this.completionResolve = null;
        this.completionReject = null;
    }
}
