import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEBUG_LOG_FILENAME, REVIEW_LOG_FILENAME } from "#src/config-paths";
import {
  DEFAULT_EXTENSION_CONFIG,
  type PermissionSystemExtensionConfig,
} from "#src/extension-config";
import type { SessionLoggerDeps } from "#src/session-logger";
import { PermissionSessionLogger } from "#src/session-logger";

// ── helpers ────────────────────────────────────────────────────────────────

let tempDir: string;

beforeEach(() => {
  tempDir = mkdtempSync(join(tmpdir(), "ps-session-logger-"));
});

function makeDeps(
  overrides: {
    globalLogsDir?: string;
    getConfig?: () => PermissionSystemExtensionConfig;
  } = {},
) {
  return {
    globalLogsDir: overrides.globalLogsDir ?? tempDir,
    getConfig:
      overrides.getConfig ??
      ((): PermissionSystemExtensionConfig => ({
        ...DEFAULT_EXTENSION_CONFIG,
      })),
    notify: vi.fn<(message: string) => void>(),
  };
}

/** A `globalLogsDir` that cannot be created: a file at the parent path blocks it. */
function makeBlockedLogsDir(): string {
  const barrier = join(tempDir, "barrier");
  writeFileSync(barrier, "");
  return join(barrier, "logs");
}

// ── PermissionSessionLogger ────────────────────────────────────────────────────

describe("PermissionSessionLogger", () => {
  // ── debug ────────────────────────────────────────────────────────────────

  describe("debug", () => {
    it("writes a JSONL line to the debug log file when debugLog is true", () => {
      const deps = makeDeps({
        getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG, debugLog: true }),
      });
      const logger = new PermissionSessionLogger(deps);

      logger.debug("test.event", { key: "value" });

      expect(existsSync(join(tempDir, DEBUG_LOG_FILENAME))).toBe(true);
      expect(deps.notify).not.toHaveBeenCalled();
    });

    it("does not write to the debug log when debugLog is false", () => {
      // DEFAULT_EXTENSION_CONFIG.debugLog === false
      const deps = makeDeps();
      const logger = new PermissionSessionLogger(deps);

      logger.debug("test.event");

      expect(existsSync(join(tempDir, DEBUG_LOG_FILENAME))).toBe(false);
      expect(deps.notify).not.toHaveBeenCalled();
    });

    it("reads getConfig at write time — a mid-session toggle change takes effect", () => {
      let debugLog = true;
      const deps = makeDeps({
        getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG, debugLog }),
      });
      const logger = new PermissionSessionLogger(deps);
      debugLog = false;

      logger.debug("test.event");

      expect(existsSync(join(tempDir, DEBUG_LOG_FILENAME))).toBe(false);
    });
  });

  // ── review ───────────────────────────────────────────────────────────────

  describe("review", () => {
    it("writes a JSONL line to the review log file when permissionReviewLog is true", () => {
      // DEFAULT_EXTENSION_CONFIG.permissionReviewLog === true
      const deps = makeDeps();
      const logger = new PermissionSessionLogger(deps);

      logger.review("permission.granted", { agentName: "coder" });

      expect(existsSync(join(tempDir, REVIEW_LOG_FILENAME))).toBe(true);
      expect(deps.notify).not.toHaveBeenCalled();
    });

    it("does not write to the review log when permissionReviewLog is false", () => {
      const deps = makeDeps({
        getConfig: () => ({
          ...DEFAULT_EXTENSION_CONFIG,
          permissionReviewLog: false,
        }),
      });
      const logger = new PermissionSessionLogger(deps);

      logger.review("permission.granted");

      expect(existsSync(join(tempDir, REVIEW_LOG_FILENAME))).toBe(false);
      expect(deps.notify).not.toHaveBeenCalled();
    });
  });

  // ── IO-failure warnings ───────────────────────────────────────────────────

  describe("IO-failure warnings", () => {
    it("calls notify with the error message when the logs directory cannot be created", () => {
      const deps = makeDeps({
        globalLogsDir: makeBlockedLogsDir(),
        getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG, debugLog: true }),
      });
      const logger = new PermissionSessionLogger(deps);

      logger.debug("test.event");

      expect(deps.notify).toHaveBeenCalledOnce();
      expect(deps.notify).toHaveBeenCalledWith(
        expect.stringContaining("Failed to"),
      );
    });

    it("deduplicates the same IO-failure warning across multiple writes", () => {
      const deps = makeDeps({
        globalLogsDir: makeBlockedLogsDir(),
        getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG, debugLog: true }),
      });
      const logger = new PermissionSessionLogger(deps);

      logger.debug("event.one");
      logger.debug("event.two");

      expect(deps.notify).toHaveBeenCalledOnce();
    });

    it("shares the dedup set across debug and review — same message notified only once", () => {
      const deps = makeDeps({
        globalLogsDir: makeBlockedLogsDir(),
        getConfig: () => ({
          ...DEFAULT_EXTENSION_CONFIG,
          debugLog: true,
          permissionReviewLog: true,
        }),
      });
      const logger = new PermissionSessionLogger(deps);

      logger.debug("event.one"); // emits warning
      logger.review("event.two"); // same error message → suppressed

      expect(deps.notify).toHaveBeenCalledOnce();
    });
  });

  // ── warn ──────────────────────────────────────────────────────────────────

  describe("warn", () => {
    it("calls notify with the message directly", () => {
      const deps = makeDeps();
      const logger = new PermissionSessionLogger(deps);

      logger.warn("Something went wrong");

      expect(deps.notify).toHaveBeenCalledWith("Something went wrong");
    });

    it("calls notify for every warn — not deduplicated", () => {
      const deps = makeDeps();
      const logger = new PermissionSessionLogger(deps);

      logger.warn("same message");
      logger.warn("same message");

      expect(deps.notify).toHaveBeenCalledTimes(2);
    });

    it("does not throw when notify is a no-op", () => {
      const deps: SessionLoggerDeps = {
        globalLogsDir: tempDir,
        getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG }),
        notify: () => {},
      };
      const logger = new PermissionSessionLogger(deps);

      expect(() => logger.warn("test")).not.toThrow();
    });
  });
});
