import { describe, test, expect, mock, beforeEach } from "bun:test";
import { buildMockUnifiSite, buildMockCompany } from "../setup";

// ---------------------------------------------------------------------------
// Stable mock factory
// ---------------------------------------------------------------------------

function createStablePrismaMock(
  overrides: Record<string, Record<string, any>> = {},
) {
  return new Proxy(
    {},
    {
      get(_target, model: string) {
        if (model === "$connect" || model === "$disconnect")
          return mock(() => Promise.resolve());
        if (overrides[model]) return overrides[model];
        return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
      },
    },
  );
}

function createMockUnifi() {
  return {
    login: mock(() => Promise.resolve()),
    getAllSites: mock(() =>
      Promise.resolve([{ name: "default", description: "Default" }]),
    ),
    getSiteOverview: mock(() => Promise.resolve({ status: "ok" })),
    getDevices: mock(() => Promise.resolve([])),
    getWlanConf: mock(() => Promise.resolve([])),
    updateWlanConf: mock(() => Promise.resolve({})),
    getNetworks: mock(() => Promise.resolve([])),
    createSite: mock(() =>
      Promise.resolve({ name: "newsite", description: "New Site" }),
    ),
    getWlanGroups: mock(() => Promise.resolve([])),
    createWlanGroup: mock(() => Promise.resolve({})),
    getUserGroups: mock(() => Promise.resolve([])),
    createUserGroup: mock(() => Promise.resolve({})),
    getApGroups: mock(() => Promise.resolve([])),
    createApGroup: mock(() => Promise.resolve({})),
    updateApGroup: mock(() => Promise.resolve({})),
    getAccessPoints: mock(() => Promise.resolve([])),
    getWifiLimits: mock(() => Promise.resolve({})),
    getPrivatePSKs: mock(() => Promise.resolve([])),
    createPrivatePSK: mock(() => Promise.resolve({})),
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("unifiSites manager", () => {
  beforeEach(() => {
    mock.restore();
  });

  // -------------------------------------------------------------------
  // fetch
  // -------------------------------------------------------------------
  describe("fetch()", () => {
    test("returns UnifiSite when found", async () => {
      const siteData = buildMockUnifiSite();
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findFirst: mock(() => Promise.resolve(siteData)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.fetch("usite-1");
      expect(result).toBeDefined();
      expect(result.id).toBe("usite-1");
    });

    test("throws 404 when not found", async () => {
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      try {
        await unifiSites.fetch("nonexistent");
        expect(true).toBe(false);
      } catch (e: any) {
        expect(e.name).toBe("UnifiSiteNotFound");
        expect(e.status).toBe(404);
      }
    });
  });

  // -------------------------------------------------------------------
  // fetchAll
  // -------------------------------------------------------------------
  describe("fetchAll()", () => {
    test("returns array of UnifiSite records", async () => {
      const sites = [buildMockUnifiSite()];
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findMany: mock(() => Promise.resolve(sites)),
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.fetchAll();
      expect(result).toBeArrayOfSize(1);
    });
  });

  // -------------------------------------------------------------------
  // fetchByCompany
  // -------------------------------------------------------------------
  describe("fetchByCompany()", () => {
    test("returns sites for a company", async () => {
      const sites = [buildMockUnifiSite({ companyId: "company-1" })];
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findMany: mock(() => Promise.resolve(sites)),
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.fetchByCompany("company-1");
      expect(result).toBeArrayOfSize(1);
    });
  });

  // -------------------------------------------------------------------
  // linkToCompany
  // -------------------------------------------------------------------
  describe("linkToCompany()", () => {
    test("links a site to a company", async () => {
      const site = buildMockUnifiSite();
      const company = buildMockCompany();
      const updatedSite = { ...site, companyId: "company-1" };

      // findFirst returns site first time, company second time
      let callCount = 0;
      const findFirstMock = mock(() => {
        callCount++;
        return Promise.resolve(callCount === 1 ? site : null);
      });

      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findFirst: mock(() => Promise.resolve(site)),
            update: mock(() => Promise.resolve(updatedSite)),
          },
          company: {
            findFirst: mock(() => Promise.resolve(company)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.linkToCompany("usite-1", "company-1");
      expect(result.companyId).toBe("company-1");
    });

    test("throws when site not found", async () => {
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findFirst: mock(() => Promise.resolve(null)),
          },
          company: {
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      try {
        await unifiSites.linkToCompany("bad-id", "company-1");
        expect(true).toBe(false);
      } catch (e: any) {
        expect(e.name).toBe("UnifiSiteNotFound");
      }
    });
  });

  // -------------------------------------------------------------------
  // unlinkFromCompany
  // -------------------------------------------------------------------
  describe("unlinkFromCompany()", () => {
    test("unlinks a site from its company", async () => {
      const site = { ...buildMockUnifiSite(), companyId: null };
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            update: mock(() => Promise.resolve(site)),
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.unlinkFromCompany("usite-1");
      expect(result.companyId).toBeNull();
    });
  });

  // -------------------------------------------------------------------
  // createSite
  // -------------------------------------------------------------------
  describe("createSite()", () => {
    test("creates site in controller and DB", async () => {
      const dbSite = buildMockUnifiSite({
        siteId: "newsite",
        name: "New Site",
      });
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            create: mock(() => Promise.resolve(dbSite)),
            findFirst: mock(() => Promise.resolve(null)),
          },
        }),
        unifi: createMockUnifi(),
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.createSite("New Site");
      expect(result).toBeDefined();
      expect(result.name).toBe("New Site");
    });
  });

  // -------------------------------------------------------------------
  // syncSites
  // -------------------------------------------------------------------
  describe("syncSites()", () => {
    test("creates and updates sites from controller", async () => {
      const existingSite = buildMockUnifiSite({ siteId: "default" });
      const updatedSite = { ...existingSite, name: "Default" };
      const newSite = buildMockUnifiSite({
        id: "usite-2",
        siteId: "site2",
        name: "Office 2",
      });

      const mockUnifi = createMockUnifi();
      mockUnifi.getAllSites = mock(() =>
        Promise.resolve([
          { name: "default", description: "Default" },
          { name: "site2", description: "Office 2" },
        ]),
      );

      let findFirstCallCount = 0;
      mock.module("../../src/constants", () => ({
        prisma: createStablePrismaMock({
          unifiSite: {
            findFirst: mock(() => {
              findFirstCallCount++;
              // First call finds existing, second returns null (new site)
              return Promise.resolve(
                findFirstCallCount === 1 ? existingSite : null,
              );
            }),
            update: mock(() => Promise.resolve(updatedSite)),
            create: mock(() => Promise.resolve(newSite)),
            findMany: mock(() => Promise.resolve([])),
          },
        }),
        unifi: mockUnifi,
        unifiUsername: "admin",
        unifiPassword: "pass",
      }));
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));

      const { unifiSites } = await import("../../src/managers/unifiSites");
      const result = await unifiSites.syncSites();
      expect(result).toBeArrayOfSize(2);
    });
  });
});
