import { waitFor } from "@testing-library/dom";
import { createHttpMockingServer } from "@hyper-fetch/testing";

import { AppManager, appManagerInitialOptions, hasDocument, hasWindow } from "managers";
import { Client } from "client";

const { resetMocks, startServer, stopServer } = createHttpMockingServer();

describe("AppManager [ Base ]", () => {
  let client = new Client({ url: "shared-base-url" });

  beforeAll(() => {
    startServer();
  });

  beforeEach(() => {
    resetMocks();
    vi.resetAllMocks();
    client = new Client({ url: "shared-base-url" });
  });

  afterAll(() => {
    stopServer();
  });

  describe("When app manager is initialized", () => {
    it("should initialize with isFocused set to true", async () => {
      expect(client.appManager.isFocused).toBeTrue();
    });
    it("should initialize with isOnline set to true", async () => {
      expect(client.appManager.isOnline).toBeTrue();
    });
    it("should initialize with custom isFocused", async () => {
      const firstManager = new AppManager({ initiallyFocused: false });
      const secondManager = new AppManager({ initiallyFocused: async () => false });
      const thirdManager = new AppManager({ initiallyFocused: undefined });

      await waitFor(() => {
        expect(firstManager.isFocused).toBeFalse();
        expect(secondManager.isFocused).toBeFalse();
        expect(thirdManager.isFocused).toBeTrue();
      });
    });
    it("should initialize with custom isOnline", async () => {
      const firstManager = new AppManager({ initiallyOnline: false });
      const secondManager = new AppManager({ initiallyOnline: async () => false });
      const thirdManager = new AppManager({ initiallyOnline: undefined });

      await waitFor(() => {
        expect(firstManager.isOnline).toBeFalse();
        expect(secondManager.isOnline).toBeFalse();
        expect(thirdManager.isOnline).toBeTrue();
      });
    });
  });
  describe("When using app manager methods", () => {
    it("should allow to change app focus state", async () => {
      const focusSpy = vi.fn();
      const blurSpy = vi.fn();
      client.appManager.events.onFocus(focusSpy);
      client.appManager.events.onBlur(blurSpy);
      client.appManager.setFocused(false);
      expect(client.appManager.isFocused).toBeFalse();
      client.appManager.setFocused(true);
      expect(client.appManager.isFocused).toBeTrue();

      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(blurSpy).toHaveBeenCalledTimes(1);
    });
    it("should allow to change app online state", async () => {
      const onlineSpy = vi.fn();
      const offlineSpy = vi.fn();
      client.appManager.events.onOnline(onlineSpy);
      client.appManager.events.onOffline(offlineSpy);
      client.appManager.setOnline(false);
      expect(client.appManager.isOnline).toBeFalse();
      client.appManager.setOnline(true);
      expect(client.appManager.isOnline).toBeTrue();

      expect(onlineSpy).toHaveBeenCalledTimes(1);
      expect(offlineSpy).toHaveBeenCalledTimes(1);
    });
    it("should properly cleanup event listeners when unmount functions are called", async () => {
      const focusSpy = vi.fn();
      const blurSpy = vi.fn();
      const onlineSpy = vi.fn();
      const offlineSpy = vi.fn();

      // Setup listeners and store cleanup functions
      const cleanupFocus = client.appManager.events.onFocus(focusSpy);
      const cleanupBlur = client.appManager.events.onBlur(blurSpy);
      const cleanupOnline = client.appManager.events.onOnline(onlineSpy);
      const cleanupOffline = client.appManager.events.onOffline(offlineSpy);

      // Trigger events once - all should fire
      client.appManager.setFocused(false);
      client.appManager.setFocused(true);
      client.appManager.setOnline(false);
      client.appManager.setOnline(true);

      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(blurSpy).toHaveBeenCalledTimes(1);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
      expect(offlineSpy).toHaveBeenCalledTimes(1);

      // Clean up all listeners
      cleanupFocus();
      cleanupBlur();
      cleanupOnline();
      cleanupOffline();

      // Trigger events again - none should fire
      client.appManager.setFocused(false);
      client.appManager.setFocused(true);
      client.appManager.setOnline(false);
      client.appManager.setOnline(true);

      // Counts should remain the same
      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(blurSpy).toHaveBeenCalledTimes(1);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
      expect(offlineSpy).toHaveBeenCalledTimes(1);
    });
  });
  describe("When using app manager utils", () => {
    it("should allow to detect window", async () => {
      expect(hasWindow()).toBeTrue();
    });
    it("should allow to detect document", async () => {
      expect(hasDocument()).toBeTrue();
    });
  });
  describe("When app manager is initialized with custom events", () => {
    it("should use custom focus event handler", async () => {
      const focusSpy = vi.fn();
      const customFocusHandler = (setFocused: (isFocused: boolean) => void) => {
        // Custom focus handler implementation
        setFocused(true);
      };

      const manager = new AppManager({ focusEvent: customFocusHandler });
      manager.events.onFocus(focusSpy);

      // Trigger the custom focus handler
      customFocusHandler(manager.setFocused);
      expect(focusSpy).toHaveBeenCalledTimes(1);
    });

    it("should use custom online event handler", async () => {
      const onlineSpy = vi.fn();
      const customOnlineHandler = (setOnline: (isOnline: boolean) => void) => {
        // Custom online handler implementation
        setOnline(true);
      };

      const manager = new AppManager({ onlineEvent: customOnlineHandler });
      manager.events.onOnline(onlineSpy);

      // Trigger the custom online handler
      customOnlineHandler(manager.setOnline);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
    });

    it("should handle state changes with custom handlers", async () => {
      const focusSpy = vi.fn();
      const blurSpy = vi.fn();

      const customFocusHandler = (setFocused: (isFocused: boolean) => void) => {
        // Test both focus and blur
        setFocused(false);
        setFocused(true);
      };

      const manager = new AppManager({ focusEvent: customFocusHandler });
      manager.events.onFocus(focusSpy);
      manager.events.onBlur(blurSpy);

      // Trigger the custom focus handler
      customFocusHandler(manager.setFocused);

      expect(blurSpy).toHaveBeenCalledTimes(1);
      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(manager.isFocused).toBeTrue();
    });
  });

  describe("When using app manager initial options", () => {
    it("should use default event handlers when not specified", async () => {
      const focusSpy = vi.fn();
      const onlineSpy = vi.fn();

      vi.spyOn(appManagerInitialOptions, "focusEvent").mockImplementation(focusSpy);
      vi.spyOn(appManagerInitialOptions, "onlineEvent").mockImplementation(onlineSpy);

      // eslint-disable-next-line no-new
      new AppManager().initialize();

      window.dispatchEvent(new Event("focus"));
      window.dispatchEvent(new Event("online"));

      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
    });
    it("should use one default event handlers when not specified", async () => {
      const focusSpy = vi.fn();
      const onlineSpy = vi.fn();

      vi.spyOn(appManagerInitialOptions, "onlineEvent").mockImplementation(onlineSpy);

      // eslint-disable-next-line no-new
      new AppManager({ focusEvent: focusSpy }).initialize();

      window.dispatchEvent(new Event("focus"));
      window.dispatchEvent(new Event("online"));

      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
    });
    it("should use other default event handlers when not specified", async () => {
      const focusSpy = vi.fn();
      const onlineSpy = vi.fn();

      vi.spyOn(appManagerInitialOptions, "focusEvent").mockImplementation(focusSpy);

      // eslint-disable-next-line no-new
      new AppManager({ onlineEvent: onlineSpy }).initialize();

      window.dispatchEvent(new Event("focus"));
      window.dispatchEvent(new Event("online"));

      expect(focusSpy).toHaveBeenCalledTimes(1);
      expect(onlineSpy).toHaveBeenCalledTimes(1);
    });
  });
});
