import { beforeEach, describe, expect, it, vi } from "vitest";
import { ConflictDetectionService } from "./conflict-detection.service";
import { ConflictDetectionError } from "./types";

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

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

describe("ConflictDetectionService", () => {
  let service: ConflictDetectionService;

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

    // Create service
    service = new ConflictDetectionService();
  });

  it("should be defined", () => {
    expect(service).toBeDefined();
  });

  describe("checkForConflicts", () => {
    it("should return no conflicts when branches can merge cleanly", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - no conflicts
      mockGit.raw.mockResolvedValue("");

      // Mock status - no conflicted files
      mockGit.status.mockResolvedValue({
        conflicted: [],
        files: [],
      });

      const result = await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        remote: "origin",
        remoteBranch: "develop",
        strategy: "merge",
      });

      expect(result.hasConflicts).toBe(false);
      expect(result.conflicts).toHaveLength(0);
      expect(result.strategy).toBe("merge");
      expect(result.remoteBranch).toBe("develop");
      expect(mockGit.fetch).toHaveBeenCalledWith("origin", "develop");
    });

    it("should detect merge conflicts", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - conflicts detected
      mockGit.raw.mockRejectedValueOnce(new Error("CONFLICT (content): Merge conflict in file.ts"));

      // Mock status - show conflicted files
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file.ts", "src/other.ts"],
        files: [
          {
            path: "src/file.ts",
            index: "U",
            working_dir: "U",
          },
          {
            path: "src/other.ts",
            index: "U",
            working_dir: "U",
          },
        ],
      });

      // Mock merge abort (cleanup)
      mockGit.raw.mockResolvedValue("");

      const result = await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        remote: "origin",
        remoteBranch: "develop",
        strategy: "merge",
      });

      expect(result.hasConflicts).toBe(true);
      expect(result.conflicts).toHaveLength(2);
      expect(result.conflicts[0].file).toBe("src/file.ts");
      expect(result.conflicts[0].type).toBe("content");
      expect(result.canRetry).toBe(true);
    });

    it("should detect rebase conflicts", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock rebase test - conflicts detected
      mockGit.raw.mockRejectedValueOnce(
        new Error("CONFLICT (content): Rebase conflict in file.ts")
      );

      // Mock status - show conflicted files
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file.ts"],
        files: [
          {
            path: "src/file.ts",
            index: "U",
            working_dir: "U",
          },
        ],
      });

      // Mock rebase abort (cleanup)
      mockGit.raw.mockResolvedValue("");

      const result = await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        remote: "origin",
        remoteBranch: "develop",
        strategy: "rebase",
      });

      expect(result.hasConflicts).toBe(true);
      expect(result.conflicts).toHaveLength(1);
      expect(result.strategy).toBe("rebase");
    });

    it("should handle fetch failure", async () => {
      // Mock fetch failure
      mockGit.fetch.mockRejectedValue(new Error("Network error"));

      await expect(
        service.checkForConflicts("/test/repo", {
          localPath: "/test/repo",
          remote: "origin",
          remoteBranch: "develop",
        })
      ).rejects.toThrow(ConflictDetectionError);
    });

    it("should detect delete conflicts", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - conflicts detected
      mockGit.raw.mockRejectedValueOnce(
        new Error("CONFLICT (delete/modify): file.ts deleted in HEAD")
      );

      // Mock status - show conflicted files with delete
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file.ts"],
        files: [
          {
            path: "src/file.ts",
            index: "D",
            working_dir: "U",
          },
        ],
      });

      // Mock merge abort
      mockGit.raw.mockResolvedValue("");

      const result = await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        remote: "origin",
        remoteBranch: "develop",
        strategy: "merge",
      });

      expect(result.hasConflicts).toBe(true);
      expect(result.conflicts[0].type).toBe("delete");
    });

    it("should detect add conflicts", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - conflicts detected
      mockGit.raw.mockRejectedValueOnce(new Error("CONFLICT (add/add): Merge conflict in file.ts"));

      // Mock status - show conflicted files with add
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file.ts"],
        files: [
          {
            path: "src/file.ts",
            index: "A",
            working_dir: "A",
          },
        ],
      });

      // Mock merge abort
      mockGit.raw.mockResolvedValue("");

      const result = await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        remote: "origin",
        remoteBranch: "develop",
        strategy: "merge",
      });

      expect(result.hasConflicts).toBe(true);
      expect(result.conflicts[0].type).toBe("add");
    });

    it("should use default values for remote and branch", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - no conflicts
      mockGit.raw.mockResolvedValue("");

      // Mock status - no conflicted files
      mockGit.status.mockResolvedValue({
        conflicted: [],
        files: [],
      });

      const result = await service.checkForConflicts("/test/repo");

      expect(result.remoteBranch).toBe("develop");
      expect(mockGit.fetch).toHaveBeenCalledWith("origin", "develop");
    });

    it("should clean up after conflict detection", async () => {
      // Mock successful fetch
      mockGit.fetch.mockResolvedValue(undefined);

      // Mock current branch
      mockGit.revparse.mockResolvedValue("feature-branch");

      // Mock merge test - conflicts
      mockGit.raw.mockRejectedValueOnce(new Error("CONFLICT"));

      // Mock status
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file.ts"],
        files: [],
      });

      // Track raw calls
      const rawCalls: string[][] = [];
      mockGit.raw.mockImplementation((args: string[]) => {
        rawCalls.push(args);
        if (args[0] === "merge") {
          if (args[1] === "--abort") {
            return Promise.resolve("");
          }
          return Promise.reject(new Error("CONFLICT"));
        }
        return Promise.resolve("");
      });

      await service.checkForConflicts("/test/repo", {
        localPath: "/test/repo",
        strategy: "merge",
      });

      // Verify abort was called
      expect(rawCalls).toContainEqual(["merge", "--abort"]);
    });
  });

  describe("fetchRemote", () => {
    it("should fetch from remote successfully", async () => {
      mockGit.fetch.mockResolvedValue(undefined);

      await service.fetchRemote("/test/repo", "origin", "develop");

      expect(mockGit.fetch).toHaveBeenCalledWith("origin", "develop");
    });

    it("should throw ConflictDetectionError on fetch failure", async () => {
      mockGit.fetch.mockRejectedValue(new Error("Network error"));

      await expect(service.fetchRemote("/test/repo", "origin", "develop")).rejects.toThrow(
        ConflictDetectionError
      );
    });

    it("should use default remote", async () => {
      mockGit.fetch.mockResolvedValue(undefined);

      await service.fetchRemote("/test/repo");

      expect(mockGit.fetch).toHaveBeenCalledWith("origin");
    });
  });

  describe("detectConflicts", () => {
    it("should return empty array when no conflicts", async () => {
      mockGit.status.mockResolvedValue({
        conflicted: [],
        files: [],
      });

      const conflicts = await service.detectConflicts("/test/repo");

      expect(conflicts).toHaveLength(0);
    });

    it("should detect conflicted files", async () => {
      mockGit.status.mockResolvedValue({
        conflicted: ["src/file1.ts", "src/file2.ts"],
        files: [
          {
            path: "src/file1.ts",
            index: "U",
            working_dir: "U",
          },
          {
            path: "src/file2.ts",
            index: "U",
            working_dir: "U",
          },
        ],
      });

      const conflicts = await service.detectConflicts("/test/repo");

      expect(conflicts).toHaveLength(2);
      expect(conflicts[0].file).toBe("src/file1.ts");
      expect(conflicts[1].file).toBe("src/file2.ts");
    });

    it("should determine conflict type from git status", async () => {
      mockGit.status.mockResolvedValue({
        conflicted: ["deleted.ts", "added.ts", "modified.ts"],
        files: [
          {
            path: "deleted.ts",
            index: "D",
            working_dir: "U",
          },
          {
            path: "added.ts",
            index: "A",
            working_dir: "A",
          },
          {
            path: "modified.ts",
            index: "U",
            working_dir: "U",
          },
        ],
      });

      const conflicts = await service.detectConflicts("/test/repo");

      expect(conflicts[0].type).toBe("delete");
      expect(conflicts[1].type).toBe("add");
      expect(conflicts[2].type).toBe("content");
    });

    it("should throw ConflictDetectionError on git status failure", async () => {
      mockGit.status.mockRejectedValue(new Error("Git error"));

      await expect(service.detectConflicts("/test/repo")).rejects.toThrow(ConflictDetectionError);
    });
  });

  describe("getCurrentBranch", () => {
    it("should return current branch name", async () => {
      mockGit.revparse.mockResolvedValue("feature-branch");

      const branch = await service.getCurrentBranch("/test/repo");

      expect(branch).toBe("feature-branch");
      expect(mockGit.revparse).toHaveBeenCalledWith(["--abbrev-ref", "HEAD"]);
    });

    it("should throw ConflictDetectionError on failure", async () => {
      mockGit.revparse.mockRejectedValue(new Error("Not a git repository"));

      await expect(service.getCurrentBranch("/test/repo")).rejects.toThrow(ConflictDetectionError);
    });
  });
});
