import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

let tempDir: string;
let templateManager: typeof import("./template-manager.js");

const mockHomedir = vi.hoisted(() => {
  let dir = "";
  return {
    get: () => dir,
    set: (d: string) => {
      dir = d;
    },
  };
});

vi.mock("node:os", async (importOriginal) => {
  const actual = await importOriginal<typeof import("node:os")>();
  return {
    ...actual,
    homedir: () => mockHomedir.get(),
  };
});

beforeEach(async () => {
  tempDir = mkdtempSync(join(tmpdir(), "template-test-"));
  mockHomedir.set(tempDir);
  vi.resetModules();
  templateManager = await import("./template-manager.js");
});

afterEach(() => {
  rmSync(tempDir, { recursive: true, force: true });
});

describe("createTemplate", () => {
  it("creates a template with required fields", () => {
    const template = templateManager.createTemplate("My Template", "A description", { backend: "claude" });
    expect(template.name).toBe("My Template");
    expect(template.description).toBe("A description");
    expect(template.config.backend).toBe("claude");
    expect(template.id).toBeTruthy();
    expect(template.createdAt).toBeGreaterThan(0);
    expect(template.updatedAt).toBeGreaterThan(0);
  });

  it("creates a template with all config fields", () => {
    const template = templateManager.createTemplate("Full Config", "", {
      backend: "codex",
      model: "o3",
      permissionMode: "full-auto",
      cwd: "/tmp/project",
      envSlug: "my-env",
      codexInternetAccess: true,
    });
    expect(template.config.backend).toBe("codex");
    expect(template.config.model).toBe("o3");
    expect(template.config.permissionMode).toBe("full-auto");
    expect(template.config.cwd).toBe("/tmp/project");
    expect(template.config.envSlug).toBe("my-env");
    expect(template.config.codexInternetAccess).toBe(true);
  });

  it("trims whitespace from name and description", () => {
    const template = templateManager.createTemplate("  Spaced  ", "  desc  ", { backend: "claude" });
    expect(template.name).toBe("Spaced");
    expect(template.description).toBe("desc");
  });

  it("rejects empty name", () => {
    expect(() => templateManager.createTemplate("", "desc", { backend: "claude" })).toThrow(
      "Template name is required",
    );
  });

  it("rejects whitespace-only name", () => {
    expect(() => templateManager.createTemplate("   ", "desc", { backend: "claude" })).toThrow(
      "Template name is required",
    );
  });
});

describe("listTemplates", () => {
  it("returns empty array with no templates", () => {
    expect(templateManager.listTemplates()).toEqual([]);
  });

  it("returns templates sorted by updatedAt desc", () => {
    const t1 = templateManager.createTemplate("First", "", { backend: "claude" });
    const t2 = templateManager.createTemplate("Second", "", { backend: "codex" });
    const list = templateManager.listTemplates();
    expect(list).toHaveLength(2);
    // Most recently created should be first
    expect(list[0].id).toBe(t2.id);
    expect(list[1].id).toBe(t1.id);
  });

  it("sorts by name when updatedAt is equal", () => {
    // Create two templates with same timestamp (mock Date.now)
    const now = Date.now();
    vi.spyOn(Date, "now").mockReturnValue(now);
    templateManager.createTemplate("Bravo", "", { backend: "claude" });
    templateManager.createTemplate("Alpha", "", { backend: "claude" });
    const list = templateManager.listTemplates();
    expect(list[0].name).toBe("Alpha");
    expect(list[1].name).toBe("Bravo");
    vi.restoreAllMocks();
  });
});

describe("getTemplate", () => {
  it("returns null for non-existent id", () => {
    expect(templateManager.getTemplate("nonexistent")).toBeNull();
  });

  it("returns template by id", () => {
    const created = templateManager.createTemplate("Test", "", { backend: "claude" });
    const found = templateManager.getTemplate(created.id);
    expect(found).not.toBeNull();
    expect(found!.name).toBe("Test");
  });
});

describe("updateTemplate", () => {
  it("returns null for non-existent id", () => {
    expect(templateManager.updateTemplate("missing", { name: "New" })).toBeNull();
  });

  it("updates name", () => {
    const created = templateManager.createTemplate("Old", "", { backend: "claude" });
    const updated = templateManager.updateTemplate(created.id, { name: "New" });
    expect(updated).not.toBeNull();
    expect(updated!.name).toBe("New");
    expect(updated!.updatedAt).toBeGreaterThanOrEqual(created.updatedAt);
  });

  it("updates description", () => {
    const created = templateManager.createTemplate("Test", "old desc", { backend: "claude" });
    const updated = templateManager.updateTemplate(created.id, { description: "new desc" });
    expect(updated!.description).toBe("new desc");
  });

  it("updates config", () => {
    const created = templateManager.createTemplate("Test", "", { backend: "claude" });
    const updated = templateManager.updateTemplate(created.id, {
      config: { backend: "codex", model: "o3" },
    });
    expect(updated!.config.backend).toBe("codex");
    expect(updated!.config.model).toBe("o3");
  });

  it("supports partial update (only name)", () => {
    const created = templateManager.createTemplate("Test", "desc", { backend: "claude", model: "sonnet" });
    const updated = templateManager.updateTemplate(created.id, { name: "Renamed" });
    expect(updated!.name).toBe("Renamed");
    expect(updated!.description).toBe("desc");
    expect(updated!.config.model).toBe("sonnet");
  });

  it("rejects empty name", () => {
    const created = templateManager.createTemplate("Test", "", { backend: "claude" });
    expect(() => templateManager.updateTemplate(created.id, { name: "" })).toThrow(
      "Template name cannot be empty",
    );
  });

  it("trims name and description", () => {
    const created = templateManager.createTemplate("Test", "", { backend: "claude" });
    const updated = templateManager.updateTemplate(created.id, {
      name: "  Trimmed  ",
      description: "  trimmed desc  ",
    });
    expect(updated!.name).toBe("Trimmed");
    expect(updated!.description).toBe("trimmed desc");
  });
});

describe("deleteTemplate", () => {
  it("returns false for non-existent id", () => {
    expect(templateManager.deleteTemplate("missing")).toBe(false);
  });

  it("deletes template and returns true", () => {
    const created = templateManager.createTemplate("ToDelete", "", { backend: "claude" });
    expect(templateManager.deleteTemplate(created.id)).toBe(true);
    expect(templateManager.getTemplate(created.id)).toBeNull();
  });

  it("persists deletion", () => {
    const t1 = templateManager.createTemplate("Keep", "", { backend: "claude" });
    const t2 = templateManager.createTemplate("Delete", "", { backend: "claude" });
    templateManager.deleteTemplate(t2.id);
    const list = templateManager.listTemplates();
    expect(list).toHaveLength(1);
    expect(list[0].id).toBe(t1.id);
  });
});

describe("edge cases", () => {
  it("handles missing file gracefully", () => {
    expect(templateManager.listTemplates()).toEqual([]);
  });

  it("handles corrupt file gracefully", () => {
    const zencodeDir = join(tempDir, ".zencode");
    const { mkdirSync } = require("node:fs");
    mkdirSync(zencodeDir, { recursive: true });
    writeFileSync(join(zencodeDir, "session-templates.json"), "not valid json");
    expect(templateManager.listTemplates()).toEqual([]);
  });

  it("handles non-array JSON gracefully", () => {
    const zencodeDir = join(tempDir, ".zencode");
    const { mkdirSync } = require("node:fs");
    mkdirSync(zencodeDir, { recursive: true });
    writeFileSync(join(zencodeDir, "session-templates.json"), '{"not":"array"}');
    expect(templateManager.listTemplates()).toEqual([]);
  });

  it("filters out invalid entries from file", () => {
    const zencodeDir = join(tempDir, ".zencode");
    const { mkdirSync } = require("node:fs");
    mkdirSync(zencodeDir, { recursive: true });
    writeFileSync(
      join(zencodeDir, "session-templates.json"),
      JSON.stringify([
        { id: "valid", name: "Valid", description: "", config: { backend: "claude" }, createdAt: 1, updatedAt: 1 },
        { id: 123, name: "Bad ID" }, // invalid: id not string
        null, // invalid: null entry
        { name: "No ID" }, // invalid: missing id
      ]),
    );
    const list = templateManager.listTemplates();
    expect(list).toHaveLength(1);
    expect(list[0].id).toBe("valid");
  });
});
