import { beforeEach, describe, expect, it, vi } from "vitest";
import { PrimariaApi } from "../../api/api";
import { PluginBusyList } from "../../api/plugin-busy-manager/plugin-busy-list/component";
import { disposeShell, raiseCloseEvent, raiseCustomCloseEvent } from "../../disposer";
import { disposePlugins } from "../../handle-plugins";
import { ExitShellHandler } from "./handler";
import { ExitShell } from "./request";

vi.mock("../../handle-plugins", () => ({
  disposePlugins: vi.fn(),
}));

vi.mock("../../disposer", () => ({
  disposeShell: vi.fn(),
  raiseCloseEvent: vi.fn(),
  raiseCustomCloseEvent: vi.fn(),
}));

const createMockApi = (): PrimariaApi =>
  ({
    exitGuardManager: {
      canExit: vi.fn().mockResolvedValue(true),
    },
    pluginBusyManager: {
      getTasks: vi.fn().mockReturnValue([]),
    },
    interactionService: {
      confirm: vi.fn(),
    },
    notificationService: {
      error: vi.fn(),
    },
  }) as any;

describe("ExitShellHandler", () => {
  let handler: ExitShellHandler;
  let mockApi: PrimariaApi;

  beforeEach(() => {
    mockApi = createMockApi();
    handler = new ExitShellHandler(mockApi);
    vi.clearAllMocks();
  });

  it("disposes and raises custom close event when canExit resolves true and message has ecapEvent", async () => {
    const message = { ecapEvent: {} } as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockResolvedValue(undefined);

    await handler.handle(message);

    expect(mockApi.exitGuardManager.canExit).toHaveBeenCalled();
    expect(disposePlugins).toHaveBeenCalled();
    expect(disposeShell).toHaveBeenCalled();
    expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
  });

  it("raises default close event when no ecapEvent is present", async () => {
    const message = {} as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockResolvedValue(undefined);

    await handler.handle(message);

    expect(disposePlugins).toHaveBeenCalled();
    expect(disposeShell).toHaveBeenCalled();
    expect(raiseCloseEvent).toHaveBeenCalled();
    expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
  });

  it("aborts the exit when any guard returns false", async () => {
    const message = { ecapEvent: {} } as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(false);

    await handler.handle(message);

    expect(mockApi.exitGuardManager.canExit).toHaveBeenCalled();
    expect(disposePlugins).not.toHaveBeenCalled();
    expect(disposeShell).not.toHaveBeenCalled();
    expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
    expect(raiseCloseEvent).not.toHaveBeenCalled();
  });

  it("notifies and raises custom close event when dispose throws and ecapEvent is present", async () => {
    const message = { ecapEvent: {} } as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockRejectedValue(new Error("boom"));

    await handler.handle(message);

    expect(mockApi.notificationService.error).toHaveBeenCalledWith("errors.exit");
    expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
    expect(raiseCloseEvent).not.toHaveBeenCalled();
  });

  it("notifies and raises default close event when dispose throws and no ecapEvent", async () => {
    const message = {} as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockRejectedValue(new Error("boom"));

    await handler.handle(message);

    expect(mockApi.notificationService.error).toHaveBeenCalledWith("errors.exit");
    expect(raiseCloseEvent).toHaveBeenCalled();
    expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
  });

  it("proceeds if disposePlugins exceeds the 10s timeout", async () => {
    const message = { ecapEvent: {} } as ExitShell;
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 11000)));

    await handler.handle(message);

    expect(disposePlugins).toHaveBeenCalled();
    expect(disposeShell).toHaveBeenCalled();
    expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
  }, 15000);

  it("raises raiseCloseEvent when no exitEvent is passed", async () => {
    (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
    (disposePlugins as any).mockResolvedValue(undefined);

    await handler.handle(undefined as any);

    expect(disposeShell).toHaveBeenCalled();
    expect(raiseCloseEvent).toHaveBeenCalled();
    expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
  });

  describe("legacy pluginBusyManager fallback", () => {
    it("asks for confirmation when canExit allows but legacy busy tasks exist", async () => {
      const busyTasks = [{ taskId: "t1", taskDescription: "saving draft" }];
      const message = { ecapEvent: {} } as ExitShell;
      (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
      (mockApi.pluginBusyManager.getTasks as any).mockReturnValue(busyTasks);
      (mockApi.interactionService.confirm as any).mockResolvedValue({ confirmed: true });
      (disposePlugins as any).mockResolvedValue(undefined);

      await handler.handle(message);

      expect(mockApi.interactionService.confirm).toHaveBeenCalledWith(
        { busyTasks },
        { component: PluginBusyList },
        {
          title: "actions.askExit",
          state: "error",
          confirmButtonText: "Sí",
          cancelButtonText: "No",
        },
      );
      expect(disposePlugins).toHaveBeenCalled();
      expect(disposeShell).toHaveBeenCalled();
      expect(raiseCustomCloseEvent).toHaveBeenCalledWith(message);
    });

    it("aborts the exit when the legacy busy-task confirmation is declined", async () => {
      const busyTasks = [{ taskId: "t1", taskDescription: "saving draft" }];
      const message = { ecapEvent: {} } as ExitShell;
      (mockApi.exitGuardManager.canExit as any).mockResolvedValue(true);
      (mockApi.pluginBusyManager.getTasks as any).mockReturnValue(busyTasks);
      (mockApi.interactionService.confirm as any).mockResolvedValue({ confirmed: false });

      await handler.handle(message);

      expect(mockApi.interactionService.confirm).toHaveBeenCalled();
      expect(disposePlugins).not.toHaveBeenCalled();
      expect(disposeShell).not.toHaveBeenCalled();
      expect(raiseCustomCloseEvent).not.toHaveBeenCalled();
      expect(raiseCloseEvent).not.toHaveBeenCalled();
    });

    it("does not consult legacy busy tasks when an ExitGuard already vetoed the exit", async () => {
      const message = { ecapEvent: {} } as ExitShell;
      (mockApi.exitGuardManager.canExit as any).mockResolvedValue(false);

      await handler.handle(message);

      expect(mockApi.pluginBusyManager.getTasks).not.toHaveBeenCalled();
      expect(mockApi.interactionService.confirm).not.toHaveBeenCalled();
    });
  });
});
