import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import path from 'path';

describe('security.ts', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    // Save original environment
    originalEnv = { ...process.env };
    // Reset modules to get fresh state
    vi.resetModules();
  });

  afterEach(() => {
    // Restore original environment
    process.env = originalEnv;
  });

  describe('initAllowedPaths', () => {
    it('should load ALLOWED_ROOT_DIRECTORY if set', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
      delete process.env.DATA_DIR;

      const { initAllowedPaths, getAllowedPaths } = await import('../src/security');
      initAllowedPaths();

      const allowed = getAllowedPaths();
      expect(allowed).toContain(path.resolve('/projects'));
    });

    it('should load DATA_DIR if set', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      process.env.DATA_DIR = '/data/directory';

      const { initAllowedPaths, getAllowedPaths } = await import('../src/security');
      initAllowedPaths();

      const allowed = getAllowedPaths();
      expect(allowed).toContain(path.resolve('/data/directory'));
    });

    it('should load both ALLOWED_ROOT_DIRECTORY and DATA_DIR if both set', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
      process.env.DATA_DIR = '/app/data';

      const { initAllowedPaths, getAllowedPaths } = await import('../src/security');
      initAllowedPaths();

      const allowed = getAllowedPaths();
      expect(allowed).toContain(path.resolve('/projects'));
      expect(allowed).toContain(path.resolve('/app/data'));
    });

    it('should handle missing environment variables gracefully', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      delete process.env.DATA_DIR;

      const { initAllowedPaths } = await import('../src/security');
      expect(() => initAllowedPaths()).not.toThrow();
    });
  });

  describe('isPathAllowed', () => {
    it('should allow paths within ALLOWED_ROOT_DIRECTORY', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/allowed';
      delete process.env.DATA_DIR;

      const { initAllowedPaths, isPathAllowed } = await import('../src/security');
      initAllowedPaths();

      expect(isPathAllowed('/allowed/file.txt')).toBe(true);
      expect(isPathAllowed('/allowed/subdir/file.txt')).toBe(true);
    });

    it('should deny paths outside ALLOWED_ROOT_DIRECTORY', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/allowed';
      delete process.env.DATA_DIR;

      const { initAllowedPaths, isPathAllowed } = await import('../src/security');
      initAllowedPaths();

      expect(isPathAllowed('/not-allowed/file.txt')).toBe(false);
      expect(isPathAllowed('/etc/passwd')).toBe(false);
    });

    it('should always allow DATA_DIR paths', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
      process.env.DATA_DIR = '/app/data';

      const { initAllowedPaths, isPathAllowed } = await import('../src/security');
      initAllowedPaths();

      // DATA_DIR paths are always allowed
      expect(isPathAllowed('/app/data/settings.json')).toBe(true);
      expect(isPathAllowed('/app/data/credentials.json')).toBe(true);
    });

    it('should allow all paths when no restrictions configured', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      delete process.env.DATA_DIR;

      const { initAllowedPaths, isPathAllowed } = await import('../src/security');
      initAllowedPaths();

      expect(isPathAllowed('/any/path')).toBe(true);
      expect(isPathAllowed('/etc/passwd')).toBe(true);
    });

    it('should allow all paths when only DATA_DIR is configured', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      process.env.DATA_DIR = '/data';

      const { initAllowedPaths, isPathAllowed } = await import('../src/security');
      initAllowedPaths();

      // DATA_DIR should be allowed
      expect(isPathAllowed('/data/file.txt')).toBe(true);
      // And all other paths should be allowed since no ALLOWED_ROOT_DIRECTORY restriction
      expect(isPathAllowed('/any/path')).toBe(true);
    });
  });

  describe('validatePath', () => {
    it('should return resolved path for allowed paths', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/allowed';
      delete process.env.DATA_DIR;

      const { initAllowedPaths, validatePath } = await import('../src/security');
      initAllowedPaths();

      const result = validatePath('/allowed/file.txt');
      expect(result).toBe(path.resolve('/allowed/file.txt'));
    });

    it('should throw error for paths outside allowed directories', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/allowed';
      delete process.env.DATA_DIR;

      const { initAllowedPaths, validatePath, PathNotAllowedError } =
        await import('../src/security');
      initAllowedPaths();

      expect(() => validatePath('/not-allowed/file.txt')).toThrow(PathNotAllowedError);
    });

    it('should resolve relative paths', async () => {
      const cwd = process.cwd();
      process.env.ALLOWED_ROOT_DIRECTORY = cwd;
      delete process.env.DATA_DIR;

      const { initAllowedPaths, validatePath } = await import('../src/security');
      initAllowedPaths();

      const result = validatePath('./file.txt');
      expect(result).toBe(path.resolve(cwd, './file.txt'));
    });

    it('should not throw when no restrictions configured', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      delete process.env.DATA_DIR;

      const { initAllowedPaths, validatePath } = await import('../src/security');
      initAllowedPaths();

      expect(() => validatePath('/any/path')).not.toThrow();
    });
  });

  describe('getAllowedPaths', () => {
    it('should return empty array when no paths configured', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;
      delete process.env.DATA_DIR;

      const { initAllowedPaths, getAllowedPaths } = await import('../src/security');
      initAllowedPaths();

      const allowed = getAllowedPaths();
      expect(Array.isArray(allowed)).toBe(true);
      expect(allowed).toHaveLength(0);
    });

    it('should return configured paths', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
      process.env.DATA_DIR = '/data';

      const { initAllowedPaths, getAllowedPaths } = await import('../src/security');
      initAllowedPaths();

      const allowed = getAllowedPaths();
      expect(allowed).toContain(path.resolve('/projects'));
      expect(allowed).toContain(path.resolve('/data'));
    });
  });

  describe('getAllowedRootDirectory', () => {
    it('should return the configured root directory', async () => {
      process.env.ALLOWED_ROOT_DIRECTORY = '/projects';

      const { initAllowedPaths, getAllowedRootDirectory } = await import('../src/security');
      initAllowedPaths();

      expect(getAllowedRootDirectory()).toBe(path.resolve('/projects'));
    });

    it('should return null when not configured', async () => {
      delete process.env.ALLOWED_ROOT_DIRECTORY;

      const { initAllowedPaths, getAllowedRootDirectory } = await import('../src/security');
      initAllowedPaths();

      expect(getAllowedRootDirectory()).toBeNull();
    });
  });

  describe('getDataDirectory', () => {
    it('should return the configured data directory', async () => {
      process.env.DATA_DIR = '/data';

      const { initAllowedPaths, getDataDirectory } = await import('../src/security');
      initAllowedPaths();

      expect(getDataDirectory()).toBe(path.resolve('/data'));
    });

    it('should return null when not configured', async () => {
      delete process.env.DATA_DIR;

      const { initAllowedPaths, getDataDirectory } = await import('../src/security');
      initAllowedPaths();

      expect(getDataDirectory()).toBeNull();
    });
  });
});
