import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Core } from "../core/backlog.ts";
import { parseTask } from "../markdown/parser.ts";
import { serializeTask } from "../markdown/serializer.ts";
import type { Task } from "../types/index.ts";

describe("Estimated Hours", () => {
	describe("Parser", () => {
		it("should parse estimatedHours from frontmatter (snake_case)", () => {
			const content = `---
id: task-1
title: "Task with hours"
status: "To Do"
estimated_hours: 4.5
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBe(4.5);
		});

		it("should parse estimatedHours from frontmatter (camelCase)", () => {
			const content = `---
id: task-2
title: "Task with hours"
status: "To Do"
estimatedHours: 8
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBe(8);
		});

		it("should prefer snake_case over camelCase", () => {
			const content = `---
id: task-3
title: "Task with both"
status: "To Do"
estimated_hours: 2
estimatedHours: 5
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBe(2);
		});

		it("should handle missing estimatedHours", () => {
			const content = `---
id: task-4
title: "Task without hours"
status: "To Do"
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBeUndefined();
		});

		it("should handle zero estimatedHours", () => {
			const content = `---
id: task-5
title: "Zero hours task"
status: "To Do"
estimated_hours: 0
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBe(0);
		});

		it("should handle integer estimatedHours", () => {
			const content = `---
id: task-6
title: "Integer hours task"
status: "To Do"
estimated_hours: 10
---

Task description.`;

			const task = parseTask(content);

			expect(task.estimatedHours).toBe(10);
		});
	});

	describe("Serializer", () => {
		it("should serialize estimatedHours to snake_case", () => {
			const task: Task = {
				id: "task-1",
				title: "Test Task",
				status: "To Do",
				assignee: [],
				createdDate: "2025-12-25",
				labels: [],
				dependencies: [],
				description: "Task description.",
				estimatedHours: 4.5,
			};

			const result = serializeTask(task);

			expect(result).toContain("estimated_hours: 4.5");
		});

		it("should not include estimatedHours if undefined", () => {
			const task: Task = {
				id: "task-2",
				title: "Test Task",
				status: "To Do",
				assignee: [],
				createdDate: "2025-12-25",
				labels: [],
				dependencies: [],
				description: "Task description.",
			};

			const result = serializeTask(task);

			expect(result).not.toContain("estimated_hours");
			expect(result).not.toContain("estimatedHours");
		});

		it("should serialize zero estimatedHours", () => {
			const task: Task = {
				id: "task-3",
				title: "Zero Hours Task",
				status: "To Do",
				assignee: [],
				createdDate: "2025-12-25",
				labels: [],
				dependencies: [],
				description: "Task description.",
				estimatedHours: 0,
			};

			const result = serializeTask(task);

			expect(result).toContain("estimated_hours: 0");
		});
	});

	describe("Round-trip", () => {
		it("should preserve estimatedHours through parse/serialize cycle", () => {
			const originalContent = `---
id: task-roundtrip
title: "Round Trip Task"
status: "In Progress"
estimated_hours: 12.75
---

## Description

This task should preserve its estimated hours.`;

			const task = parseTask(originalContent);
			expect(task.estimatedHours).toBe(12.75);

			const serialized = serializeTask(task);
			expect(serialized).toContain("estimated_hours: 12.75");

			const reparsed = parseTask(serialized);
			expect(reparsed.estimatedHours).toBe(12.75);
		});
	});

	describe("Core API", () => {
		let testDir: string;
		let core: Core;

		beforeEach(async () => {
			testDir = mkdtempSync(join(tmpdir(), "backlog-estimated-hours-test-"));
			core = new Core(testDir);
			await core.filesystem.ensureBacklogStructure();

			// Write config with valid statuses
			const configContent = `project_name: Test Project
statuses:
  - To Do
  - In Progress
  - Done
labels: []
milestones: []
`;
			await writeFile(join(testDir, "backlog", "config.yml"), configContent);
		});

		afterEach(() => {
			try {
				rmSync(testDir, { recursive: true, force: true });
			} catch {
				// Ignore cleanup errors
			}
		});

		it("should create task with estimatedHours", async () => {
			const task = await core.createTaskFromData(
				{
					title: "Task with hours",
					status: "To Do",
					estimatedHours: 6,
				},
				false, // disable auto-commit
			);

			expect(task.estimatedHours).toBe(6);
		});

		it("should update task estimatedHours", async () => {
			const task = await core.createTaskFromData(
				{
					title: "Task to update",
					status: "To Do",
					estimatedHours: 4,
				},
				false,
			);

			const updated = await core.updateTaskFromInput(
				task.id,
				{
					estimatedHours: 8,
				},
				false,
			);

			expect(updated.estimatedHours).toBe(8);
		});

		it("should preserve estimatedHours when updating other fields", async () => {
			const task = await core.createTaskFromData(
				{
					title: "Task with hours",
					status: "To Do",
					estimatedHours: 10,
				},
				false,
			);

			const updated = await core.updateTaskFromInput(
				task.id,
				{
					status: "In Progress",
				},
				false,
			);

			expect(updated.estimatedHours).toBe(10);
			expect(updated.status).toBe("In Progress");
		});

		it("should reject negative estimatedHours", async () => {
			const task = await core.createTaskFromData(
				{
					title: "Task to test",
					status: "To Do",
				},
				false,
			);

			expect(
				core.updateTaskFromInput(
					task.id,
					{
						estimatedHours: -5,
					},
					false,
				),
			).rejects.toThrow();
		});
	});
});
