import { describe, test, expect, beforeEach, afterEach, vi, type MockInstance } from "vitest"
import { log, logInitStep, logInputFile } from "../src/logger"
import { LogLevel, ScaffoldConfig } from "../src/types"

describe("logger", () => {
  let consoleSpy: {
    log: MockInstance
    warn: MockInstance
    error: MockInstance
  }

  beforeEach(() => {
    consoleSpy = {
      log: vi.spyOn(console, "log").mockImplementation(() => void 0),
      warn: vi.spyOn(console, "warn").mockImplementation(() => void 0),
      error: vi.spyOn(console, "error").mockImplementation(() => void 0),
    }
  })

  afterEach(() => {
    consoleSpy.log.mockRestore()
    consoleSpy.warn.mockRestore()
    consoleSpy.error.mockRestore()
  })

  describe("log", () => {
    test("does not log when logLevel is none", () => {
      log({ logLevel: LogLevel.none }, LogLevel.info, "test")
      expect(consoleSpy.log).not.toHaveBeenCalled()
    })

    test("logs info messages with console.log", () => {
      log({ logLevel: LogLevel.info }, LogLevel.info, "test message")
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("logs warning messages with console.warn", () => {
      log({ logLevel: LogLevel.warning }, LogLevel.warning, "warning message")
      expect(consoleSpy.warn).toHaveBeenCalled()
    })

    test("logs error messages with console.error", () => {
      log({ logLevel: LogLevel.error }, LogLevel.error, "error message")
      expect(consoleSpy.error).toHaveBeenCalled()
    })

    test("filters out messages below configured level", () => {
      log({ logLevel: LogLevel.warning }, LogLevel.info, "should be filtered")
      expect(consoleSpy.log).not.toHaveBeenCalled()
    })

    test("filters out debug messages when level is info", () => {
      log({ logLevel: LogLevel.info }, LogLevel.debug, "debug message")
      expect(consoleSpy.log).not.toHaveBeenCalled()
    })

    test("shows debug messages when level is debug", () => {
      log({ logLevel: LogLevel.debug }, LogLevel.debug, "debug message")
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("shows all levels when configured as debug", () => {
      log({ logLevel: LogLevel.debug }, LogLevel.debug, "d")
      log({ logLevel: LogLevel.debug }, LogLevel.info, "i")
      log({ logLevel: LogLevel.debug }, LogLevel.warning, "w")
      log({ logLevel: LogLevel.debug }, LogLevel.error, "e")
      expect(consoleSpy.log).toHaveBeenCalledTimes(2) // debug + info
      expect(consoleSpy.warn).toHaveBeenCalledTimes(1)
      expect(consoleSpy.error).toHaveBeenCalledTimes(1)
    })

    test("handles Error objects", () => {
      log({ logLevel: LogLevel.error }, LogLevel.error, new Error("test error"))
      expect(consoleSpy.error).toHaveBeenCalled()
    })

    test("handles objects with util.inspect", () => {
      log({ logLevel: LogLevel.info }, LogLevel.info, { key: "value" })
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("handles multiple arguments", () => {
      log({ logLevel: LogLevel.info }, LogLevel.info, "a", "b", "c")
      expect(consoleSpy.log).toHaveBeenCalled()
      // First call, should have 3 arguments
      expect(consoleSpy.log.mock.calls[0].length).toBe(3)
    })

    test("defaults to info when logLevel is undefined", () => {
      log({ logLevel: undefined as unknown as LogLevel }, LogLevel.info, "test")
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("error level shows when logLevel is info", () => {
      log({ logLevel: LogLevel.info }, LogLevel.error, "error")
      expect(consoleSpy.error).toHaveBeenCalled()
    })

    test("warning level shows when logLevel is info", () => {
      log({ logLevel: LogLevel.info }, LogLevel.warning, "warning")
      expect(consoleSpy.warn).toHaveBeenCalled()
    })
  })

  describe("logInitStep", () => {
    test("logs config at debug level", () => {
      const config: ScaffoldConfig = {
        name: "test",
        output: "output",
        templates: ["input"],
        logLevel: LogLevel.debug,
        data: { name: "test" },
      }
      logInitStep(config)
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("does not log at info level (debug only)", () => {
      const config: ScaffoldConfig = {
        name: "test",
        output: "output",
        templates: ["input"],
        logLevel: LogLevel.info,
        data: { name: "test" },
      }
      logInitStep(config)
      // Full config is debug-only, nothing logged at info
      expect(consoleSpy.log).not.toHaveBeenCalled()
    })
  })

  describe("logInputFile", () => {
    test("logs file info at debug level", () => {
      const config: ScaffoldConfig = {
        name: "test",
        output: "output",
        templates: ["input"],
        logLevel: LogLevel.debug,
        data: { name: "test" },
      }
      logInputFile(config, {
        originalTemplate: "input",
        relativePath: ".",
        parsedTemplate: "input/**/*",
        inputFilePath: "input/file.txt",
        nonGlobTemplate: "input",
        basePath: "",
        isDirOrGlob: true,
        isGlob: false,
      })
      expect(consoleSpy.log).toHaveBeenCalled()
    })

    test("does not log at info level", () => {
      const config: ScaffoldConfig = {
        name: "test",
        output: "output",
        templates: ["input"],
        logLevel: LogLevel.info,
        data: { name: "test" },
      }
      logInputFile(config, {
        originalTemplate: "input",
        relativePath: ".",
        parsedTemplate: "input/**/*",
        inputFilePath: "input/file.txt",
        nonGlobTemplate: "input",
        basePath: "",
        isDirOrGlob: true,
        isGlob: false,
      })
      expect(consoleSpy.log).not.toHaveBeenCalled()
    })
  })
})
