import { describe, it, expect, beforeEach, vi } from 'vitest';
import { FilePermissionChecker } from '../../../src/permission/checkers/file.js';
import type { PermissionDecision, FilePermissionContext } from '../../../src/permission/types.js';

// Mock fs 以避免实际文件操作
vi.mock('fs', () => ({
  existsSync: vi.fn(() => false),
  readFileSync: vi.fn(),
  writeFileSync: vi.fn(),
  mkdirSync: vi.fn(),
}));

// Mock file-prompt
vi.mock('../../../src/permission/file-prompt.js', () => ({
  promptFilePermission: vi.fn().mockResolvedValue({ allow: true, remember: false }),
}));

describe('FilePermissionChecker - 文件权限检查器', () => {
  let checker: FilePermissionChecker;
  const testProjectRoot = '/test/project';

  beforeEach(() => {
    checker = new FilePermissionChecker(testProjectRoot);
    vi.clearAllMocks();
  });

  describe('默认配置', () => {
    it('加载默认配置', () => {
      const config = checker.getConfig();

      expect(config.operations).toBeDefined();
      expect(config.sensitivePaths).toBeDefined();
      expect(config.externalDirectory).toBe('ask');
    });

    it('读操作默认允许', () => {
      const config = checker.getConfig();

      expect(config.operations.read).toBe('allow');
      expect(config.operations.list).toBe('allow');
      expect(config.operations.search).toBe('allow');
      expect(config.operations.grep).toBe('allow');
      expect(config.operations.info).toBe('allow');
    });

    it('写操作默认需要确认', () => {
      const config = checker.getConfig();

      expect(config.operations.write).toBe('ask');
      expect(config.operations.edit).toBe('ask');
      expect(config.operations.move).toBe('ask');
      expect(config.operations.copy).toBe('ask');
      expect(config.operations.delete).toBe('ask');
      expect(config.operations.mkdir).toBe('ask');
    });
  });

  describe('读操作权限', () => {
    const readOperations: FilePermissionContext['operation'][] = [
      'read',
      'list',
      'search',
      'grep',
      'info',
    ];

    for (const operation of readOperations) {
      it(`${operation} 操作在项目内默认允许`, async () => {
        const result = await checker.checkFilePermission({
          operation,
          path: './src/index.ts',
          workdir: testProjectRoot,
        });

        expect(result.allowed).toBe(true);
        expect(result.action).toBe('allow');
      });
    }
  });

  describe('写操作权限', () => {
    const writeOperations: FilePermissionContext['operation'][] = [
      'write',
      'edit',
      'move',
      'copy',
      'delete',
      'mkdir',
    ];

    for (const operation of writeOperations) {
      it(`${operation} 操作无回调时需要确认`, async () => {
        const result = await checker.checkFilePermission({
          operation,
          path: './src/new-file.ts',
          workdir: testProjectRoot,
        });

        expect(result.action).toBe('ask');
        expect(result.needsConfirmation).toBe(true);
      });
    }
  });

  describe('敏感路径检查', () => {
    it('系统路径拒绝访问', async () => {
      const sensitivePaths = [
        '/etc/passwd',
        '/usr/bin/node',
        '/bin/sh',
        '/var/log/syslog',
      ];

      for (const testPath of sensitivePaths) {
        const result = await checker.checkFilePermission({
          operation: 'read',
          path: testPath,
          workdir: testProjectRoot,
        });

        expect(result.allowed).toBe(false);
        expect(result.action).toBe('deny');
      }
    });

    it('用户敏感文件需要确认', async () => {
      const sensitivePaths = [
        '~/.ssh/id_rsa',
        '~/.aws/credentials',
        './.env',
      ];

      for (const testPath of sensitivePaths) {
        const result = await checker.checkFilePermission({
          operation: 'read',
          path: testPath,
          workdir: testProjectRoot,
        });

        // 敏感路径会触发 ask
        expect(result.action === 'ask' || result.action === 'deny').toBe(true);
      }
    });
  });

  describe('外部目录访问', () => {
    it('项目外路径需要确认', async () => {
      const result = await checker.checkFilePermission({
        operation: 'read',
        path: '/home/other/file.txt',
        workdir: testProjectRoot,
      });

      // 外部目录会触发 ask 或 deny
      expect(result.action === 'ask' || result.action === 'deny').toBe(true);
    });

    it('项目内路径正常检查', async () => {
      const result = await checker.checkFilePermission({
        operation: 'read',
        path: './src/index.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(true);
    });
  });

  describe('波浪号展开', () => {
    it('展开 ~ 为 home 目录', async () => {
      const result = await checker.checkFilePermission({
        operation: 'read',
        path: '~/projects/file.txt',
        workdir: testProjectRoot,
      });

      // 外部路径会触发 ask
      expect(result).toBeDefined();
    });
  });

  describe('回调处理', () => {
    it('用户允许时返回允许', async () => {
      const mockCallback = vi.fn().mockResolvedValue({
        allow: true,
        remember: false,
      } as PermissionDecision);

      checker.setAskCallback(mockCallback);

      const result = await checker.checkFilePermission({
        operation: 'write',
        path: './src/new-file.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(true);
    });

    it('用户拒绝时返回拒绝', async () => {
      const mockCallback = vi.fn().mockResolvedValue({
        allow: false,
        remember: false,
      } as PermissionDecision);

      checker.setAskCallback(mockCallback);

      const result = await checker.checkFilePermission({
        operation: 'delete',
        path: './src/file.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(false);
      expect(result.action).toBe('deny');
    });
  });

  describe('会话权限管理', () => {
    it('记住允许决定', async () => {
      const mockCallback = vi.fn().mockResolvedValue({
        allow: true,
        remember: true,
      } as PermissionDecision);

      checker.setAskCallback(mockCallback);

      // 第一次调用
      await checker.checkFilePermission({
        operation: 'write',
        path: './src/test.ts',
        workdir: testProjectRoot,
      });

      // 第二次调用同一操作和路径
      const result = await checker.checkFilePermission({
        operation: 'write',
        path: './src/test.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(true);
      // 第二次不应该调用回调
      expect(mockCallback).toHaveBeenCalledTimes(1);
    });

    it('记住拒绝决定', async () => {
      const mockCallback = vi.fn().mockResolvedValue({
        allow: false,
        remember: true,
      } as PermissionDecision);

      checker.setAskCallback(mockCallback);

      // 第一次调用
      await checker.checkFilePermission({
        operation: 'delete',
        path: './src/important.ts',
        workdir: testProjectRoot,
      });

      // 第二次调用
      const result = await checker.checkFilePermission({
        operation: 'delete',
        path: './src/important.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(false);
      expect(mockCallback).toHaveBeenCalledTimes(1);
    });

    it('清除会话权限后重新询问', async () => {
      const mockCallback = vi.fn().mockResolvedValue({
        allow: true,
        remember: true,
      } as PermissionDecision);

      checker.setAskCallback(mockCallback);

      // 第一次调用
      await checker.checkFilePermission({
        operation: 'write',
        path: './src/test.ts',
        workdir: testProjectRoot,
      });

      // 清除权限
      checker.clearSessionPermissions();

      // 再次调用
      await checker.checkFilePermission({
        operation: 'write',
        path: './src/test.ts',
        workdir: testProjectRoot,
      });

      // 应该调用两次
      expect(mockCallback).toHaveBeenCalledTimes(2);
    });
  });

  describe('check 接口（兼容 PermissionChecker）', () => {
    it('解析 read 操作', async () => {
      const result = await checker.check({
        command: 'read ./src/index.ts',
        workdir: testProjectRoot,
      });

      expect(result.allowed).toBe(true);
    });

    it('解析 write 操作', async () => {
      const result = await checker.check({
        command: 'write ./src/new.ts',
        workdir: testProjectRoot,
      });

      expect(result.action).toBe('ask');
    });
  });
});
