import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
import { loadRemoteTasks, resolveTaskConflict } from "../core/task-loader.ts";
import type { GitOperations } from "../git/operations.ts";
import type { Task } from "../types/index.ts";

// Mock GitOperations for testing
class MockGitOperations implements Partial<GitOperations> {
	async fetch(): Promise<void> {
		// Mock fetch
	}

	async listRemoteBranches(): Promise<string[]> {
		return ["main", "feature", "feature2"];
	}

	async listRecentRemoteBranches(_daysAgo: number): Promise<string[]> {
		return ["main", "feature", "feature2"];
	}

	async getBranchLastModifiedMap(_ref: string, _dir: string): Promise<Map<string, Date>> {
		const map = new Map<string, Date>();
		// Add all files with the same date for simplicity
		map.set("backlog/tasks/task-1 - Main Task.md", new Date("2025-06-13"));
		map.set("backlog/tasks/task-2 - Feature Task.md", new Date("2025-06-13"));
		map.set("backlog/tasks/task-3 - Feature2 Task.md", new Date("2025-06-13"));
		return map;
	}

	async listFilesInTree(ref: string, _path: string): Promise<string[]> {
		if (ref === "origin/main") {
			return ["backlog/tasks/task-1 - Main Task.md"];
		}
		if (ref === "origin/feature") {
			return ["backlog/tasks/task-2 - Feature Task.md"];
		}
		if (ref === "origin/feature2") {
			return ["backlog/tasks/task-3 - Feature2 Task.md"];
		}
		return [];
	}

	async showFile(_ref: string, file: string): Promise<string> {
		if (file.includes("task-1")) {
			return `---
id: task-1
title: Main Task
status: To Do
assignee: []
created_date: 2025-06-13
labels: []
dependencies: []
---\n\n## Description\n\nMain task`;
		}
		if (file.includes("task-2")) {
			return `---
id: task-2
title: Feature Task
status: In Progress
assignee: []
created_date: 2025-06-13
labels: []
dependencies: []
---\n\n## Description\n\nFeature task`;
		}
		if (file.includes("task-3")) {
			return `---
id: task-3
title: Feature2 Task
status: Done
assignee: []
created_date: 2025-06-13
labels: []
dependencies: []
---\n\n## Description\n\nFeature2 task`;
		}
		return "";
	}

	async getFileLastModifiedTime(_ref: string, _file: string): Promise<Date | null> {
		return new Date("2025-06-13");
	}
}

describe("Parallel remote task loading", () => {
	let consoleErrorSpy: ReturnType<typeof spyOn>;

	beforeEach(() => {
		consoleErrorSpy = spyOn(console, "error");
	});

	afterEach(() => {
		consoleErrorSpy?.mockRestore();
	});

	it("should load tasks from multiple branches in parallel", async () => {
		const mockGitOperations = new MockGitOperations() as unknown as GitOperations;

		// Track progress messages
		const progressMessages: string[] = [];
		const remoteTasks = await loadRemoteTasks(mockGitOperations, null, (msg: string) => {
			progressMessages.push(msg);
		});

		// Verify results - we should have tasks from all remote branches
		expect(remoteTasks.length).toBe(3);
		const taskIds = remoteTasks.map((t) => t.id);
		expect(taskIds).toContain("TASK-1");
		expect(taskIds).toContain("TASK-2");
		expect(taskIds).toContain("TASK-3");

		// Verify each task has correct metadata
		const task1 = remoteTasks.find((t) => t.id === "TASK-1");
		expect(task1?.source).toBe("remote");
		expect(task1?.branch).toBe("main");
		expect(task1?.status).toBe("To Do");

		// Verify progress reporting
		expect(progressMessages.some((msg) => msg.includes("Fetching remote branches"))).toBe(true);
		expect(progressMessages.some((msg) => msg.includes("Found 3 unique tasks across remote branches"))).toBe(true);
		expect(progressMessages.some((msg) => msg.includes("Loaded 3 remote tasks"))).toBe(true);
	});

	it("should handle errors gracefully", async () => {
		// Create a mock that throws an error
		const errorGitOperations = {
			fetch: async () => {
				throw new Error("Network error");
			},
			listRecentRemoteBranches: async (_daysAgo: number) => {
				throw new Error("Network error");
			},
		} as unknown as GitOperations;

		// Should return empty array on error
		const remoteTasks = await loadRemoteTasks(errorGitOperations, null);
		expect(remoteTasks).toEqual([]);

		// Verify error was logged
		expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
		expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to fetch remote tasks:", expect.any(Error));
	});

	it("should index and hydrate remote tasks via pinned SHA when the branch moves after resolving", async () => {
		const SHA = "feedfacecafe0000feedfacecafe0000feedface";
		const mockGitOperations = {
			fetch: async () => {},
			listRecentRemoteBranches: async () => ["feature-gone"],
			resolveCommit: async (ref: string) => (ref === "origin/feature-gone" ? SHA : null),
			listFilesInTree: async (ref: string) => {
				if (ref !== SHA) {
					throw new Error(`unexpected list ref ${ref}`);
				}
				return ["backlog/tasks/task-8 - Remote Task.md"];
			},
			getBranchLastModifiedMap: async (ref: string) => {
				if (ref !== SHA) {
					throw new Error(`unexpected timestamp ref ${ref}`);
				}
				return new Map([["backlog/tasks/task-8 - Remote Task.md", new Date("2025-06-13")]]);
			},
			showFile: async (ref: string) => {
				if (ref !== SHA) {
					throw new Error(`unexpected show ref ${ref}`);
				}
				return `---
id: task-8
title: Remote Task
status: In Progress
assignee: []
created_date: 2025-06-13
labels: []
dependencies: []
---\n\n## Description\n\nPinned remote task`;
			},
		} as unknown as GitOperations;

		const localTasks: Task[] = [
			{
				id: "TASK-8",
				title: "Local Remote Task",
				status: "To Do",
				assignee: [],
				createdDate: "2025-06-01",
				updatedDate: "2025-06-01",
				labels: [],
				dependencies: [],
				source: "local",
			},
		];

		const remoteTasks = await loadRemoteTasks(mockGitOperations, null, undefined, localTasks);

		const task8 = remoteTasks.find((task) => task.id === "TASK-8");
		expect(task8).toBeDefined();
		expect(task8?.title).toBe("Remote Task");
		expect(task8?.source).toBe("remote");
		expect(task8?.branch).toBe("feature-gone");
	});

	it("should resolve task conflicts correctly", async () => {
		const statuses = ["To Do", "In Progress", "Done"];

		const localTask: Task = {
			id: "task-1",
			title: "Local Task",
			status: "To Do",
			assignee: [],
			createdDate: "2025-06-13",
			labels: [],
			dependencies: [],
			description: "Local version",
			source: "local",
			lastModified: new Date("2025-06-13T10:00:00Z"),
		};

		const remoteTask: Task = {
			id: "task-1",
			title: "Remote Task",
			status: "Done",
			assignee: [],
			createdDate: "2025-06-13",
			labels: [],
			dependencies: [],
			description: "Remote version",
			source: "remote",
			branch: "feature",
			lastModified: new Date("2025-06-13T12:00:00Z"),
		};

		// Test most_progressed strategy - should pick Done over To Do
		const resolved1 = resolveTaskConflict(localTask, remoteTask, statuses, "most_progressed");
		expect(resolved1.status).toBe("Done");
		expect(resolved1.title).toBe("Remote Task");

		// Test most_recent strategy - should pick the more recent one
		const resolved2 = resolveTaskConflict(localTask, remoteTask, statuses, "most_recent");
		expect(resolved2.lastModified).toEqual(new Date("2025-06-13T12:00:00Z"));
		expect(resolved2.title).toBe("Remote Task");
	});
});
