import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
  readWorktreeMetadata,
  writeWorktreeMetadata,
  updateWorktreePRInfo,
  getWorktreePRInfo,
  readAllWorktreeMetadata,
  deleteWorktreeMetadata,
  type WorktreeMetadata,
  type WorktreePRInfo,
} from '@/lib/worktree-metadata.js';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';

describe('worktree-metadata.ts', () => {
  let testProjectPath: string;

  beforeEach(async () => {
    testProjectPath = path.join(os.tmpdir(), `worktree-metadata-test-${Date.now()}`);
    await fs.mkdir(testProjectPath, { recursive: true });
  });

  afterEach(async () => {
    try {
      await fs.rm(testProjectPath, { recursive: true, force: true });
    } catch {
      // Ignore cleanup errors
    }
  });

  describe('sanitizeBranchName', () => {
    // Test through readWorktreeMetadata and writeWorktreeMetadata
    it('should sanitize branch names with invalid characters', async () => {
      const branch = 'feature/test-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should sanitize branch names with Windows invalid characters', async () => {
      const branch = 'feature:test*branch?';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should sanitize Windows reserved names', async () => {
      const branch = 'CON';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should handle empty branch name', async () => {
      const branch = '';
      const metadata: WorktreeMetadata = {
        branch: 'branch',
        createdAt: new Date().toISOString(),
      };

      // Empty branch name should be sanitized to "_branch"
      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should handle branch name that becomes empty after sanitization', async () => {
      // Test branch that would become empty after removing invalid chars
      const branch = '///';
      const metadata: WorktreeMetadata = {
        branch: 'branch',
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });
  });

  describe('readWorktreeMetadata', () => {
    it("should return null when metadata file doesn't exist", async () => {
      const result = await readWorktreeMetadata(testProjectPath, 'nonexistent-branch');
      expect(result).toBeNull();
    });

    it('should read existing metadata', async () => {
      const branch = 'test-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should read metadata with PR info', async () => {
      const branch = 'pr-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
        pr: {
          number: 123,
          url: 'https://github.com/owner/repo/pull/123',
          title: 'Test PR',
          state: 'open',
          createdAt: new Date().toISOString(),
        },
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });
  });

  describe('writeWorktreeMetadata', () => {
    it("should create metadata directory if it doesn't exist", async () => {
      const branch = 'new-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata);
    });

    it('should overwrite existing metadata', async () => {
      const branch = 'existing-branch';
      const metadata1: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };
      const metadata2: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
        pr: {
          number: 456,
          url: 'https://github.com/owner/repo/pull/456',
          title: 'Updated PR',
          state: 'closed',
          createdAt: new Date().toISOString(),
        },
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata1);
      await writeWorktreeMetadata(testProjectPath, branch, metadata2);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toEqual(metadata2);
    });
  });

  describe('updateWorktreePRInfo', () => {
    it("should create new metadata if it doesn't exist", async () => {
      const branch = 'new-pr-branch';
      const prInfo: WorktreePRInfo = {
        number: 789,
        url: 'https://github.com/owner/repo/pull/789',
        title: 'New PR',
        state: 'open',
        createdAt: new Date().toISOString(),
      };

      await updateWorktreePRInfo(testProjectPath, branch, prInfo);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).not.toBeNull();
      expect(result?.branch).toBe(branch);
      expect(result?.pr).toEqual(prInfo);
    });

    it('should update existing metadata with PR info', async () => {
      const branch = 'existing-pr-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);

      const prInfo: WorktreePRInfo = {
        number: 999,
        url: 'https://github.com/owner/repo/pull/999',
        title: 'Updated PR',
        state: 'merged',
        createdAt: new Date().toISOString(),
      };

      await updateWorktreePRInfo(testProjectPath, branch, prInfo);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result?.pr).toEqual(prInfo);
    });

    it('should preserve existing metadata when updating PR info', async () => {
      const branch = 'preserve-branch';
      const originalCreatedAt = new Date().toISOString();
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: originalCreatedAt,
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);

      const prInfo: WorktreePRInfo = {
        number: 111,
        url: 'https://github.com/owner/repo/pull/111',
        title: 'PR',
        state: 'open',
        createdAt: new Date().toISOString(),
      };

      await updateWorktreePRInfo(testProjectPath, branch, prInfo);
      const result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result?.createdAt).toBe(originalCreatedAt);
      expect(result?.pr).toEqual(prInfo);
    });
  });

  describe('getWorktreePRInfo', () => {
    it("should return null when metadata doesn't exist", async () => {
      const result = await getWorktreePRInfo(testProjectPath, 'nonexistent');
      expect(result).toBeNull();
    });

    it('should return null when metadata exists but has no PR info', async () => {
      const branch = 'no-pr-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      const result = await getWorktreePRInfo(testProjectPath, branch);
      expect(result).toBeNull();
    });

    it('should return PR info when it exists', async () => {
      const branch = 'has-pr-branch';
      const prInfo: WorktreePRInfo = {
        number: 222,
        url: 'https://github.com/owner/repo/pull/222',
        title: 'Has PR',
        state: 'open',
        createdAt: new Date().toISOString(),
      };

      await updateWorktreePRInfo(testProjectPath, branch, prInfo);
      const result = await getWorktreePRInfo(testProjectPath, branch);
      expect(result).toEqual(prInfo);
    });
  });

  describe('readAllWorktreeMetadata', () => {
    it("should return empty map when worktrees directory doesn't exist", async () => {
      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(0);
    });

    it('should return empty map when worktrees directory is empty', async () => {
      const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees');
      await fs.mkdir(worktreesDir, { recursive: true });

      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(0);
    });

    it('should read all worktree metadata', async () => {
      const branch1 = 'branch-1';
      const branch2 = 'branch-2';
      const metadata1: WorktreeMetadata = {
        branch: branch1,
        createdAt: new Date().toISOString(),
      };
      const metadata2: WorktreeMetadata = {
        branch: branch2,
        createdAt: new Date().toISOString(),
        pr: {
          number: 333,
          url: 'https://github.com/owner/repo/pull/333',
          title: 'PR 3',
          state: 'open',
          createdAt: new Date().toISOString(),
        },
      };

      await writeWorktreeMetadata(testProjectPath, branch1, metadata1);
      await writeWorktreeMetadata(testProjectPath, branch2, metadata2);

      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(2);
      expect(result.get(branch1)).toEqual(metadata1);
      expect(result.get(branch2)).toEqual(metadata2);
    });

    it('should skip directories without worktree.json', async () => {
      const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees');
      const emptyDir = path.join(worktreesDir, 'empty-dir');
      await fs.mkdir(emptyDir, { recursive: true });

      const branch = 'valid-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };
      await writeWorktreeMetadata(testProjectPath, branch, metadata);

      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(1);
      expect(result.get(branch)).toEqual(metadata);
    });

    it('should skip files in worktrees directory', async () => {
      const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees');
      await fs.mkdir(worktreesDir, { recursive: true });
      const filePath = path.join(worktreesDir, 'not-a-dir.txt');
      await fs.writeFile(filePath, 'content');

      const branch = 'valid-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };
      await writeWorktreeMetadata(testProjectPath, branch, metadata);

      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(1);
      expect(result.get(branch)).toEqual(metadata);
    });

    it('should skip directories with malformed JSON', async () => {
      const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees');
      const badDir = path.join(worktreesDir, 'bad-dir');
      await fs.mkdir(badDir, { recursive: true });
      const badJsonPath = path.join(badDir, 'worktree.json');
      await fs.writeFile(badJsonPath, 'not valid json');

      const branch = 'valid-branch';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };
      await writeWorktreeMetadata(testProjectPath, branch, metadata);

      const result = await readAllWorktreeMetadata(testProjectPath);
      expect(result.size).toBe(1);
      expect(result.get(branch)).toEqual(metadata);
    });
  });

  describe('deleteWorktreeMetadata', () => {
    it('should delete worktree metadata directory', async () => {
      const branch = 'to-delete';
      const metadata: WorktreeMetadata = {
        branch,
        createdAt: new Date().toISOString(),
      };

      await writeWorktreeMetadata(testProjectPath, branch, metadata);
      let result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).not.toBeNull();

      await deleteWorktreeMetadata(testProjectPath, branch);
      result = await readWorktreeMetadata(testProjectPath, branch);
      expect(result).toBeNull();
    });

    it("should handle deletion when metadata doesn't exist", async () => {
      // Should not throw
      await expect(deleteWorktreeMetadata(testProjectPath, 'nonexistent')).resolves.toBeUndefined();
    });
  });
});
