import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"

beforeEach(() => {
  localStorage.clear()
})

afterEach(() => {
  vi.restoreAllMocks()
})

async function freshLogger() {
  vi.resetModules()
  return import("@/lib/logger")
}

describe("createLogger", () => {
  it("logs debug when global filter is wildcard", async () => {
    localStorage.setItem("editor:debug", "*")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("test message")
    expect(spy).toHaveBeenCalledWith("[MyFeature]", "test message")
  })

  it("logs info when global filter is wildcard", async () => {
    localStorage.setItem("editor:debug", "*")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("info message")
    expect(spy).toHaveBeenCalledWith("[MyFeature]", "info message")
  })

  it("does not log debug when no global filter matches", async () => {
    localStorage.setItem("editor:debug", "OtherNamespace")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("should not appear")
    expect(spy).not.toHaveBeenCalled()
  })

  it("logs when namespace matches specific global filter", async () => {
    localStorage.setItem("editor:debug", "Block,MyFeature,Parse")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("visible")
    expect(spy).toHaveBeenCalled()
  })

  it("does not log for non-matching namespace in comma-separated filter", async () => {
    localStorage.setItem("editor:debug", "Block,Parse")
    const { createLogger } = await freshLogger()
    const log = createLogger("Other")
    const spy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("invisible")
    expect(spy).not.toHaveBeenCalled()
  })
})

describe("component filter", () => {
  it("component filter wildcard logs regardless of global", async () => {
    localStorage.setItem("editor:debug", "Unrelated")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature", "*")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("visible via component filter")
    expect(spy).toHaveBeenCalled()
  })

  it("component filter with matching namespace logs", async () => {
    localStorage.setItem("editor:debug", "Unrelated")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature", "MyFeature,Other")
    const spy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("visible")
    expect(spy).toHaveBeenCalled()
  })

  it("component filter with non-matching namespace suppresses logs", async () => {
    localStorage.setItem("editor:debug", "*")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature", "Other")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("suppressed by component filter")
    expect(spy).not.toHaveBeenCalled()
  })
})

describe("level filtering", () => {
  it("does not log debug when minLevel is info", async () => {
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("should not appear")
    expect(spy).not.toHaveBeenCalled()
  })

  it("logs warn when namespace matches global filter", async () => {
    localStorage.setItem("editor:debug", "MyFeature")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "warn").mockImplementation(() => {})
    log.warn("warning is visible")
    expect(spy).toHaveBeenCalled()
  })

  it("logs error when namespace matches global filter", async () => {
    localStorage.setItem("editor:debug", "MyFeature")
    const { createLogger } = await freshLogger()
    const log = createLogger("MyFeature")
    const spy = vi.spyOn(console, "error").mockImplementation(() => {})
    log.error("error is always visible")
    expect(spy).toHaveBeenCalled()
  })
})

describe("configureDebug", () => {
  it("updates namespace at runtime", async () => {
    const { configureDebug, createLogger } = await freshLogger()
    configureDebug({ namespaces: "RuntimeNS", minLevel: "debug" })

    const matchingLog = createLogger("RuntimeNS")
    const spyMatch = vi.spyOn(console, "debug").mockImplementation(() => {})
    matchingLog.debug("matches runtime config")
    expect(spyMatch).toHaveBeenCalled()
    spyMatch.mockRestore()

    const nonMatchingLog = createLogger("Other")
    const spyNoMatch = vi.spyOn(console, "debug").mockImplementation(() => {})
    nonMatchingLog.debug("does not match")
    expect(spyNoMatch).not.toHaveBeenCalled()
  })

  it("updates minLevel at runtime to allow debug", async () => {
    const { configureDebug, createLogger } = await freshLogger()
    configureDebug({ namespaces: "*", minLevel: "debug" })

    const log = createLogger("Any")
    const spy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("now visible")
    expect(spy).toHaveBeenCalled()
  })

  it("updates minLevel at runtime to suppress info", async () => {
    const { configureDebug, createLogger } = await freshLogger()
    configureDebug({ namespaces: "*", minLevel: "warn" })

    const log = createLogger("Any")
    const infoSpy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("suppressed by minLevel")
    expect(infoSpy).not.toHaveBeenCalled()

    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
    log.warn("warn is above minLevel")
    expect(warnSpy).toHaveBeenCalled()
  })
})

describe("default config", () => {
  it("uses default config when localStorage is empty", async () => {
    const { createLogger } = await freshLogger()
    const log = createLogger("Any")
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
    log.warn("warn visible by default")
    expect(warnSpy).toHaveBeenCalled()

    const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {})
    log.debug("debug not visible by default")
    expect(debugSpy).not.toHaveBeenCalled()
  })

  it("handles localStorage unavailable gracefully", async () => {
    const getItem = vi
      .spyOn(Storage.prototype, "getItem")
      .mockImplementation(() => {
        throw new Error("storage unavailable")
      })
    const { createLogger } = await freshLogger()
    const log = createLogger("Any")
    const spy = vi.spyOn(console, "info").mockImplementation(() => {})
    log.info("still works")
    expect(spy).toHaveBeenCalled()
    getItem.mockRestore()
  })
})
