import { ConfigService } from "@nestjs/config";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { WorktreeManagerService } from "./worktree-manager.service";
import { GitOperationsService } from "./git-operations.service";
import { WorktreeError } from "./types";
import * as path from "path";

// Mock simple-git
const mockGit = {
  raw: vi.fn(),
};

vi.mock("simple-git", () => ({
  simpleGit: vi.fn(() => mockGit),
}));

describe("WorktreeManagerService", () => {
  let service: WorktreeManagerService;
  let mockConfigService: ConfigService;
  let mockGitOperationsService: GitOperationsService;

  beforeEach(() => {
    // Reset all mocks
    vi.clearAllMocks();

    // Create mock config service
    mockConfigService = {
      get: vi.fn((key: string) => {
        if (key === "orchestrator.git.userName") return "Test User";
        if (key === "orchestrator.git.userEmail") return "test@example.com";
        return undefined;
      }),
    } as unknown as ConfigService;

    // Create mock git operations service
    mockGitOperationsService = new GitOperationsService(mockConfigService);

    // Create service with mocks
    service = new WorktreeManagerService(mockGitOperationsService);
  });

  describe("createWorktree", () => {
    it("should create worktree with correct naming convention", async () => {
      const repoPath = "/tmp/test-repo";
      const agentId = "agent-123";
      const taskId = "task-456";
      const expectedPath = path.join("/tmp", "test-repo_worktrees", `agent-${agentId}-${taskId}`);
      const branchName = `agent-${agentId}-${taskId}`;

      mockGit.raw.mockResolvedValue(
        `worktree ${expectedPath}\nHEAD abc123\nbranch refs/heads/${branchName}`
      );

      const result = await service.createWorktree(repoPath, agentId, taskId);

      expect(result).toBeDefined();
      expect(result.path).toBe(expectedPath);
      expect(result.branch).toBe(branchName);
      expect(mockGit.raw).toHaveBeenCalledWith([
        "worktree",
        "add",
        expectedPath,
        "-b",
        branchName,
        "develop",
      ]);
    });

    it("should create worktree with custom base branch", async () => {
      const repoPath = "/tmp/test-repo";
      const agentId = "agent-123";
      const taskId = "task-456";
      const baseBranch = "main";
      const expectedPath = path.join("/tmp", "test-repo_worktrees", `agent-${agentId}-${taskId}`);
      const branchName = `agent-${agentId}-${taskId}`;

      mockGit.raw.mockResolvedValue(
        `worktree ${expectedPath}\nHEAD abc123\nbranch refs/heads/${branchName}`
      );

      await service.createWorktree(repoPath, agentId, taskId, baseBranch);

      expect(mockGit.raw).toHaveBeenCalledWith([
        "worktree",
        "add",
        expectedPath,
        "-b",
        branchName,
        baseBranch,
      ]);
    });

    it("should throw WorktreeError if worktree already exists", async () => {
      const error = new Error("fatal: 'agent-123-task-456' already exists");
      mockGit.raw.mockRejectedValue(error);

      await expect(
        service.createWorktree("/tmp/test-repo", "agent-123", "task-456")
      ).rejects.toThrow(WorktreeError);

      try {
        await service.createWorktree("/tmp/test-repo", "agent-123", "task-456");
      } catch (e) {
        expect(e).toBeInstanceOf(WorktreeError);
        expect((e as WorktreeError).operation).toBe("createWorktree");
        expect((e as WorktreeError).cause).toBe(error);
      }
    });

    it("should throw WorktreeError on git command failure", async () => {
      const error = new Error("git command failed");
      mockGit.raw.mockRejectedValue(error);

      await expect(
        service.createWorktree("/tmp/test-repo", "agent-123", "task-456")
      ).rejects.toThrow(WorktreeError);
    });

    it("should validate agentId is not empty", async () => {
      await expect(service.createWorktree("/tmp/test-repo", "", "task-456")).rejects.toThrow(
        "agentId is required"
      );
    });

    it("should validate taskId is not empty", async () => {
      await expect(service.createWorktree("/tmp/test-repo", "agent-123", "")).rejects.toThrow(
        "taskId is required"
      );
    });

    it("should validate repoPath is not empty", async () => {
      await expect(service.createWorktree("", "agent-123", "task-456")).rejects.toThrow(
        "repoPath is required"
      );
    });
  });

  describe("removeWorktree", () => {
    it("should remove worktree successfully", async () => {
      const worktreePath = "/tmp/test-repo_worktrees/agent-123-task-456";
      mockGit.raw.mockResolvedValue("");

      await service.removeWorktree(worktreePath);

      expect(mockGit.raw).toHaveBeenCalledWith(["worktree", "remove", worktreePath, "--force"]);
    });

    it("should handle non-existent worktree gracefully", async () => {
      const worktreePath = "/tmp/test-repo_worktrees/non-existent";
      const error = new Error("fatal: 'non-existent' is not a working tree");
      mockGit.raw.mockRejectedValue(error);

      // Should not throw, just log warning
      await expect(service.removeWorktree(worktreePath)).resolves.not.toThrow();
    });

    it("should throw WorktreeError on removal failure", async () => {
      const worktreePath = "/tmp/test-repo_worktrees/agent-123-task-456";
      const error = new Error("permission denied");
      mockGit.raw.mockRejectedValue(error);

      // Should throw for non-worktree-not-found errors
      await expect(service.removeWorktree(worktreePath)).rejects.toThrow();
    });

    it("should validate worktreePath is not empty", async () => {
      await expect(service.removeWorktree("")).rejects.toThrow("worktreePath is required");
    });
  });

  describe("listWorktrees", () => {
    it("should return empty array when no worktrees exist", async () => {
      const repoPath = "/tmp/test-repo";
      mockGit.raw.mockResolvedValue(`/tmp/test-repo abc123 [develop]`);

      const result = await service.listWorktrees(repoPath);

      expect(result).toEqual([]);
    });

    it("should list all active worktrees", async () => {
      const repoPath = "/tmp/test-repo";
      const output = `/tmp/test-repo abc123 [develop]
/tmp/test-repo_worktrees/agent-123-task-456 def456 [agent-123-task-456]
/tmp/test-repo_worktrees/agent-789-task-012 abc789 [agent-789-task-012]`;

      mockGit.raw.mockResolvedValue(output);

      const result = await service.listWorktrees(repoPath);

      expect(result).toHaveLength(2);
      expect(result[0].path).toBe("/tmp/test-repo_worktrees/agent-123-task-456");
      expect(result[0].commit).toBe("def456");
      expect(result[0].branch).toBe("agent-123-task-456");
      expect(result[1].path).toBe("/tmp/test-repo_worktrees/agent-789-task-012");
      expect(result[1].commit).toBe("abc789");
      expect(result[1].branch).toBe("agent-789-task-012");
    });

    it("should parse worktree info correctly", async () => {
      const repoPath = "/tmp/test-repo";
      const output = `/tmp/test-repo abc123 [develop]
/tmp/test-repo_worktrees/agent-123-task-456 def456 [agent-123-task-456]`;

      mockGit.raw.mockResolvedValue(output);

      const result = await service.listWorktrees(repoPath);

      expect(result[0]).toEqual({
        path: "/tmp/test-repo_worktrees/agent-123-task-456",
        commit: "def456",
        branch: "agent-123-task-456",
      });
    });

    it("should throw WorktreeError on git command failure", async () => {
      const error = new Error("git command failed");
      mockGit.raw.mockRejectedValue(error);

      await expect(service.listWorktrees("/tmp/test-repo")).rejects.toThrow(WorktreeError);
    });

    it("should validate repoPath is not empty", async () => {
      await expect(service.listWorktrees("")).rejects.toThrow("repoPath is required");
    });
  });

  describe("cleanupWorktree", () => {
    it("should remove worktree on agent completion and return success", async () => {
      const repoPath = "/tmp/test-repo";
      const agentId = "agent-123";
      const taskId = "task-456";
      const worktreePath = path.join("/tmp", "test-repo_worktrees", `agent-${agentId}-${taskId}`);

      mockGit.raw.mockResolvedValue("");

      const result = await service.cleanupWorktree(repoPath, agentId, taskId);

      expect(result).toEqual({ success: true });
      expect(mockGit.raw).toHaveBeenCalledWith(["worktree", "remove", worktreePath, "--force"]);
    });

    it("should return failure result on cleanup errors", async () => {
      const error = new Error("worktree not found");
      mockGit.raw.mockRejectedValue(error);

      const result = await service.cleanupWorktree("/tmp/test-repo", "agent-123", "task-456");

      expect(result.success).toBe(false);
      expect(result.error).toContain("Failed to remove worktree");
    });

    it("should handle non-Error objects in cleanup errors", async () => {
      mockGit.raw.mockRejectedValue("string error");

      const result = await service.cleanupWorktree("/tmp/test-repo", "agent-123", "task-456");

      expect(result.success).toBe(false);
      expect(result.error).toContain("Failed to remove worktree");
    });

    it("should validate agentId is not empty", async () => {
      await expect(service.cleanupWorktree("/tmp/test-repo", "", "task-456")).rejects.toThrow(
        "agentId is required"
      );
    });

    it("should validate taskId is not empty", async () => {
      await expect(service.cleanupWorktree("/tmp/test-repo", "agent-123", "")).rejects.toThrow(
        "taskId is required"
      );
    });

    it("should validate repoPath is not empty", async () => {
      await expect(service.cleanupWorktree("", "agent-123", "task-456")).rejects.toThrow(
        "repoPath is required"
      );
    });
  });

  describe("getWorktreePath", () => {
    it("should generate correct worktree path", () => {
      const repoPath = "/tmp/test-repo";
      const agentId = "agent-123";
      const taskId = "task-456";
      const expectedPath = path.join("/tmp", "test-repo_worktrees", `agent-${agentId}-${taskId}`);

      const result = service.getWorktreePath(repoPath, agentId, taskId);

      expect(result).toBe(expectedPath);
    });

    it("should handle repo paths with trailing slashes", () => {
      const repoPath = "/tmp/test-repo/";
      const agentId = "agent-123";
      const taskId = "task-456";
      const expectedPath = path.join("/tmp", "test-repo_worktrees", `agent-${agentId}-${taskId}`);

      const result = service.getWorktreePath(repoPath, agentId, taskId);

      expect(result).toBe(expectedPath);
    });
  });

  describe("getBranchName", () => {
    it("should generate correct branch name", () => {
      const agentId = "agent-123";
      const taskId = "task-456";
      const expectedBranch = `agent-${agentId}-${taskId}`;

      const result = service.getBranchName(agentId, taskId);

      expect(result).toBe(expectedBranch);
    });
  });
});
