import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { get } from "svelte/store";
import { emitMockEvent } from "../../../vitest.setup";
import { debugConsoleStore, filteredLogs } from "./debugConsole";

describe("debugConsoleStore", () => {
  beforeEach(() => {
    debugConsoleStore.clear();
    debugConsoleStore.close();
    debugConsoleStore.setFilterLevel("all");
    debugConsoleStore.setAutoScroll(true);
  });

  afterEach(() => {
    debugConsoleStore.restoreConsole();
    debugConsoleStore.clear();
  });

  it("initializes with correct default state", () => {
    const state = get(debugConsoleStore);
    expect(state.logs).toEqual([]);
    expect(state.isOpen).toBe(false);
    expect(state.maxLogs).toBe(1000);
    expect(state.filterLevel).toBe("all");
    expect(state.autoScroll).toBe(true);
  });

  describe("toggle", () => {
    it("opens when currently closed", () => {
      debugConsoleStore.toggle();
      expect(get(debugConsoleStore).isOpen).toBe(true);
    });

    it("closes when currently open", () => {
      debugConsoleStore.open();
      debugConsoleStore.toggle();
      expect(get(debugConsoleStore).isOpen).toBe(false);
    });
  });

  describe("open", () => {
    it("sets isOpen to true", () => {
      debugConsoleStore.open();
      expect(get(debugConsoleStore).isOpen).toBe(true);
    });
  });

  describe("close", () => {
    it("sets isOpen to false", () => {
      debugConsoleStore.open();
      debugConsoleStore.close();
      expect(get(debugConsoleStore).isOpen).toBe(false);
    });
  });

  describe("clear", () => {
    it("removes all log entries", async () => {
      await debugConsoleStore.setupBackendLogsListener();
      emitMockEvent("debug:log", { level: "info", message: "test entry" });
      expect(get(debugConsoleStore).logs.length).toBe(1);
      debugConsoleStore.clear();
      expect(get(debugConsoleStore).logs).toEqual([]);
    });
  });

  describe("setFilterLevel", () => {
    it("updates filterLevel to the specified level", () => {
      debugConsoleStore.setFilterLevel("error");
      expect(get(debugConsoleStore).filterLevel).toBe("error");
    });

    it("can reset filterLevel back to all", () => {
      debugConsoleStore.setFilterLevel("warn");
      debugConsoleStore.setFilterLevel("all");
      expect(get(debugConsoleStore).filterLevel).toBe("all");
    });
  });

  describe("setAutoScroll", () => {
    it("disables autoScroll", () => {
      debugConsoleStore.setAutoScroll(false);
      expect(get(debugConsoleStore).autoScroll).toBe(false);
    });

    it("re-enables autoScroll", () => {
      debugConsoleStore.setAutoScroll(false);
      debugConsoleStore.setAutoScroll(true);
      expect(get(debugConsoleStore).autoScroll).toBe(true);
    });
  });

  describe("setupConsoleCapture", () => {
    afterEach(() => {
      debugConsoleStore.restoreConsole();
    });

    it("captures console.log calls as info-level frontend logs", () => {
      debugConsoleStore.setupConsoleCapture();
      console.log("hello world");
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "hello world");
      expect(captured).toBeDefined();
      expect(captured?.level).toBe("info");
      expect(captured?.source).toBe("frontend");
    });

    it("captures console.info calls as info-level logs", () => {
      debugConsoleStore.setupConsoleCapture();
      console.info("info message");
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "info message");
      expect(captured?.level).toBe("info");
    });

    it("captures console.warn calls as warn-level logs", () => {
      debugConsoleStore.setupConsoleCapture();
      console.warn("warning message");
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "warning message");
      expect(captured?.level).toBe("warn");
    });

    it("captures console.error calls as error-level logs", () => {
      debugConsoleStore.setupConsoleCapture();
      console.error("error message");
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "error message");
      expect(captured?.level).toBe("error");
    });

    it("captures console.debug calls as debug-level logs", () => {
      debugConsoleStore.setupConsoleCapture();
      console.debug("debug message");
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "debug message");
      expect(captured?.level).toBe("debug");
    });

    it("joins multiple console arguments with spaces", () => {
      debugConsoleStore.setupConsoleCapture();
      console.log("hello", "world", 42);
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find((l) => l.message === "hello world 42");
      expect(captured).toBeDefined();
    });

    it("captures unhandled window error events", () => {
      debugConsoleStore.setupConsoleCapture();
      const errorEvent = new ErrorEvent("error", {
        message: "Test unhandled error",
        filename: "test.js",
        lineno: 10,
        colno: 5,
      });
      window.dispatchEvent(errorEvent);
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find(
        (l) => l.level === "error" && l.message.includes("[Unhandled Error]")
      );
      expect(captured).toBeDefined();
      expect(captured?.message).toContain("Test unhandled error");
    });

    it("captures unhandled promise rejection events", () => {
      debugConsoleStore.setupConsoleCapture();
      const rejectionEvent = Object.assign(new Event("unhandledrejection"), {
        reason: "test rejection reason",
      });
      window.dispatchEvent(rejectionEvent);
      const logs = get(debugConsoleStore).logs;
      const captured = logs.find(
        (l) => l.level === "error" && l.message.includes("[Unhandled Promise Rejection]")
      );
      expect(captured).toBeDefined();
      expect(captured?.message).toContain("test rejection reason");
    });
  });

  describe("restoreConsole", () => {
    it("stops capturing console output after restore", () => {
      debugConsoleStore.setupConsoleCapture();
      debugConsoleStore.restoreConsole();
      const countBefore = get(debugConsoleStore).logs.length;
      console.log("this should not be captured");
      expect(get(debugConsoleStore).logs.length).toBe(countBefore);
    });
  });

  describe("setupBackendLogsListener", () => {
    it("captures backend logs emitted via debug:log event", async () => {
      await debugConsoleStore.setupBackendLogsListener();
      emitMockEvent("debug:log", { level: "info", message: "backend message" });
      const logs = get(debugConsoleStore).logs;
      expect(logs.length).toBe(1);
      expect(logs[0].level).toBe("info");
      expect(logs[0].message).toBe("backend message");
      expect(logs[0].source).toBe("backend");
    });

    it("handles different log levels from backend", async () => {
      await debugConsoleStore.setupBackendLogsListener();
      emitMockEvent("debug:log", { level: "error", message: "backend error" });
      const logs = get(debugConsoleStore).logs;
      expect(logs[0].level).toBe("error");
    });
  });

  describe("circular buffer", () => {
    it("drops oldest log when exceeding 1000-entry limit", async () => {
      await debugConsoleStore.setupBackendLogsListener();
      for (let i = 0; i < 1001; i++) {
        emitMockEvent("debug:log", { level: "info", message: `log ${i}` });
      }
      const logs = get(debugConsoleStore).logs;
      expect(logs.length).toBe(1000);
      expect(logs[0].message).toBe("log 1");
      expect(logs[999].message).toBe("log 1000");
    });
  });
});

describe("filteredLogs derived store", () => {
  beforeEach(async () => {
    debugConsoleStore.clear();
    debugConsoleStore.setFilterLevel("all");
    await debugConsoleStore.setupBackendLogsListener();
  });

  afterEach(() => {
    debugConsoleStore.clear();
  });

  it("returns all logs when filterLevel is all", () => {
    emitMockEvent("debug:log", { level: "debug", message: "d" });
    emitMockEvent("debug:log", { level: "info", message: "i" });
    emitMockEvent("debug:log", { level: "warn", message: "w" });
    emitMockEvent("debug:log", { level: "error", message: "e" });
    expect(get(filteredLogs).length).toBe(4);
  });

  it("returns only error logs when filterLevel is error", () => {
    emitMockEvent("debug:log", { level: "debug", message: "d" });
    emitMockEvent("debug:log", { level: "info", message: "i" });
    emitMockEvent("debug:log", { level: "warn", message: "w" });
    emitMockEvent("debug:log", { level: "error", message: "e" });
    debugConsoleStore.setFilterLevel("error");
    const logs = get(filteredLogs);
    expect(logs.length).toBe(1);
    expect(logs[0].level).toBe("error");
  });

  it("returns warn and error logs when filterLevel is warn", () => {
    emitMockEvent("debug:log", { level: "debug", message: "d" });
    emitMockEvent("debug:log", { level: "info", message: "i" });
    emitMockEvent("debug:log", { level: "warn", message: "w" });
    emitMockEvent("debug:log", { level: "error", message: "e" });
    debugConsoleStore.setFilterLevel("warn");
    const logs = get(filteredLogs);
    expect(logs.length).toBe(2);
    expect(logs.every((l) => l.level === "warn" || l.level === "error")).toBe(true);
  });

  it("excludes debug logs when filterLevel is info", () => {
    emitMockEvent("debug:log", { level: "debug", message: "d" });
    emitMockEvent("debug:log", { level: "info", message: "i" });
    emitMockEvent("debug:log", { level: "warn", message: "w" });
    emitMockEvent("debug:log", { level: "error", message: "e" });
    debugConsoleStore.setFilterLevel("info");
    const logs = get(filteredLogs);
    expect(logs.length).toBe(3);
    expect(logs.some((l) => l.level === "debug")).toBe(false);
  });

  it("returns all log levels when filterLevel is debug", () => {
    emitMockEvent("debug:log", { level: "debug", message: "d" });
    emitMockEvent("debug:log", { level: "info", message: "i" });
    emitMockEvent("debug:log", { level: "warn", message: "w" });
    emitMockEvent("debug:log", { level: "error", message: "e" });
    debugConsoleStore.setFilterLevel("debug");
    expect(get(filteredLogs).length).toBe(4);
  });
});
