import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import os from 'os';
import { CursorConfigManager } from '@/providers/cursor-config-manager.js';

vi.mock('fs');
vi.mock('@automaker/platform', () => ({
  getAutomakerDir: vi.fn((projectPath: string) => path.join(projectPath, '.automaker')),
}));

describe('cursor-config-manager.ts', () => {
  // Use platform-agnostic paths
  const testProjectPath = path.join(os.tmpdir(), 'test-project');
  const expectedConfigPath = path.join(testProjectPath, '.automaker', 'cursor-config.json');
  let manager: CursorConfigManager;

  beforeEach(() => {
    vi.clearAllMocks();
    // Default: no existing config file
    vi.mocked(fs.existsSync).mockReturnValue(false);
    vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
    vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  describe('constructor', () => {
    it('should load existing config from disk', () => {
      const existingConfig = {
        defaultModel: 'claude-3-5-sonnet',
        models: ['auto', 'claude-3-5-sonnet'],
      };

      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(existingConfig));

      manager = new CursorConfigManager(testProjectPath);

      expect(fs.existsSync).toHaveBeenCalledWith(expectedConfigPath);
      expect(fs.readFileSync).toHaveBeenCalledWith(expectedConfigPath, 'utf8');
      expect(manager.getConfig()).toEqual(existingConfig);
    });

    it('should use default config if file does not exist', () => {
      vi.mocked(fs.existsSync).mockReturnValue(false);

      manager = new CursorConfigManager(testProjectPath);

      const config = manager.getConfig();
      expect(config.defaultModel).toBe('auto');
      expect(config.models).toContain('auto');
    });

    it('should use default config if file read fails', () => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockImplementation(() => {
        throw new Error('Read error');
      });

      manager = new CursorConfigManager(testProjectPath);

      expect(manager.getDefaultModel()).toBe('auto');
    });

    it('should use default config if JSON parse fails', () => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue('invalid json');

      manager = new CursorConfigManager(testProjectPath);

      expect(manager.getDefaultModel()).toBe('auto');
    });
  });

  describe('getConfig', () => {
    it('should return a copy of the config', () => {
      manager = new CursorConfigManager(testProjectPath);

      const config1 = manager.getConfig();
      const config2 = manager.getConfig();

      expect(config1).toEqual(config2);
      expect(config1).not.toBe(config2); // Different objects
    });
  });

  describe('getDefaultModel / setDefaultModel', () => {
    beforeEach(() => {
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should return default model', () => {
      expect(manager.getDefaultModel()).toBe('auto');
    });

    it('should set and persist default model', () => {
      manager.setDefaultModel('claude-3-5-sonnet');

      expect(manager.getDefaultModel()).toBe('claude-3-5-sonnet');
      expect(fs.writeFileSync).toHaveBeenCalled();
    });

    it('should return auto if defaultModel is undefined', () => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ models: ['auto'] }));

      manager = new CursorConfigManager(testProjectPath);

      expect(manager.getDefaultModel()).toBe('auto');
    });
  });

  describe('getEnabledModels / setEnabledModels', () => {
    beforeEach(() => {
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should return enabled models', () => {
      const models = manager.getEnabledModels();
      expect(Array.isArray(models)).toBe(true);
      expect(models).toContain('auto');
    });

    it('should set enabled models', () => {
      manager.setEnabledModels(['claude-3-5-sonnet', 'gpt-4o']);

      expect(manager.getEnabledModels()).toEqual(['claude-3-5-sonnet', 'gpt-4o']);
      expect(fs.writeFileSync).toHaveBeenCalled();
    });

    it('should return [auto] if models is undefined', () => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ defaultModel: 'auto' }));

      manager = new CursorConfigManager(testProjectPath);

      expect(manager.getEnabledModels()).toEqual(['auto']);
    });
  });

  describe('addModel', () => {
    beforeEach(() => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(
        JSON.stringify({
          defaultModel: 'auto',
          models: ['auto'],
        })
      );
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should add a new model', () => {
      manager.addModel('claude-3-5-sonnet');

      expect(manager.getEnabledModels()).toContain('claude-3-5-sonnet');
      expect(fs.writeFileSync).toHaveBeenCalled();
    });

    it('should not add duplicate models', () => {
      manager.addModel('auto');

      // Should not save if model already exists
      expect(fs.writeFileSync).not.toHaveBeenCalled();
    });

    it('should initialize models array if undefined', () => {
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ defaultModel: 'auto' }));
      manager = new CursorConfigManager(testProjectPath);

      manager.addModel('claude-3-5-sonnet');

      expect(manager.getEnabledModels()).toContain('claude-3-5-sonnet');
    });
  });

  describe('removeModel', () => {
    beforeEach(() => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(
        JSON.stringify({
          defaultModel: 'auto',
          models: ['auto', 'claude-3-5-sonnet', 'gpt-4o'],
        })
      );
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should remove a model', () => {
      manager.removeModel('gpt-4o');

      expect(manager.getEnabledModels()).not.toContain('gpt-4o');
      expect(fs.writeFileSync).toHaveBeenCalled();
    });

    it('should handle removing non-existent model', () => {
      manager.removeModel('non-existent' as any);

      // Should still save (filtering happens regardless)
      expect(fs.writeFileSync).toHaveBeenCalled();
    });

    it('should do nothing if models array is undefined', () => {
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ defaultModel: 'auto' }));
      manager = new CursorConfigManager(testProjectPath);

      manager.removeModel('auto');

      expect(fs.writeFileSync).not.toHaveBeenCalled();
    });
  });

  describe('isModelEnabled', () => {
    beforeEach(() => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(
        JSON.stringify({
          defaultModel: 'auto',
          models: ['auto', 'claude-3-5-sonnet'],
        })
      );
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should return true for enabled model', () => {
      expect(manager.isModelEnabled('auto')).toBe(true);
      expect(manager.isModelEnabled('claude-3-5-sonnet')).toBe(true);
    });

    it('should return false for disabled model', () => {
      expect(manager.isModelEnabled('gpt-4o')).toBe(false);
    });

    it('should return false if models is undefined', () => {
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ defaultModel: 'auto' }));
      manager = new CursorConfigManager(testProjectPath);

      expect(manager.isModelEnabled('auto')).toBe(false);
    });
  });

  describe('getMcpServers / setMcpServers', () => {
    beforeEach(() => {
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should return empty array by default', () => {
      expect(manager.getMcpServers()).toEqual([]);
    });

    it('should set and get MCP servers', () => {
      manager.setMcpServers(['server1', 'server2']);

      expect(manager.getMcpServers()).toEqual(['server1', 'server2']);
      expect(fs.writeFileSync).toHaveBeenCalled();
    });
  });

  describe('getRules / setRules', () => {
    beforeEach(() => {
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should return empty array by default', () => {
      expect(manager.getRules()).toEqual([]);
    });

    it('should set and get rules', () => {
      manager.setRules(['.cursorrules', 'rules.md']);

      expect(manager.getRules()).toEqual(['.cursorrules', 'rules.md']);
      expect(fs.writeFileSync).toHaveBeenCalled();
    });
  });

  describe('reset', () => {
    beforeEach(() => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      vi.mocked(fs.readFileSync).mockReturnValue(
        JSON.stringify({
          defaultModel: 'claude-3-5-sonnet',
          models: ['claude-3-5-sonnet'],
          mcpServers: ['server1'],
          rules: ['rules.md'],
        })
      );
      manager = new CursorConfigManager(testProjectPath);
    });

    it('should reset to default values', () => {
      manager.reset();

      expect(manager.getDefaultModel()).toBe('auto');
      expect(manager.getMcpServers()).toEqual([]);
      expect(manager.getRules()).toEqual([]);
      expect(fs.writeFileSync).toHaveBeenCalled();
    });
  });

  describe('exists', () => {
    it('should return true if config file exists', () => {
      vi.mocked(fs.existsSync).mockReturnValue(true);
      manager = new CursorConfigManager(testProjectPath);

      vi.mocked(fs.existsSync).mockReturnValue(true);
      expect(manager.exists()).toBe(true);
    });

    it('should return false if config file does not exist', () => {
      manager = new CursorConfigManager(testProjectPath);

      vi.mocked(fs.existsSync).mockReturnValue(false);
      expect(manager.exists()).toBe(false);
    });
  });

  describe('getConfigPath', () => {
    it('should return the config file path', () => {
      manager = new CursorConfigManager(testProjectPath);

      expect(manager.getConfigPath()).toBe(expectedConfigPath);
    });
  });

  describe('saveConfig', () => {
    it('should create directory if it does not exist', () => {
      vi.mocked(fs.existsSync)
        .mockReturnValueOnce(false) // For loadConfig
        .mockReturnValueOnce(false); // For directory check in saveConfig

      manager = new CursorConfigManager(testProjectPath);
      manager.setDefaultModel('claude-3-5-sonnet');

      expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(expectedConfigPath), {
        recursive: true,
      });
    });

    it('should throw error on write failure', () => {
      manager = new CursorConfigManager(testProjectPath);

      vi.mocked(fs.writeFileSync).mockImplementation(() => {
        throw new Error('Write failed');
      });

      expect(() => manager.setDefaultModel('claude-3-5-sonnet')).toThrow('Write failed');
    });
  });
});
