import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { SimpleProxyManager } from '../src/simple-proxy-manager';
import type { ProxyInfo } from '../src/types';

describe('ProxyManager', () => {
  let manager: SimpleProxyManager;

  const getMockProxies = (): ProxyInfo[] => [
    {
      id: 'proxy1',
      host: '1.2.3.4',
      port: 8080,
      protocol: 'http',
      username: 'user1',
      password: 'pass1',
      active: true,
    },
    {
      id: 'proxy2',
      host: '5.6.7.8',
      port: 8080,
      protocol: 'http',
      username: 'user2',
      password: 'pass2',
      active: true,
    },
    {
      id: 'proxy3',
      host: '9.10.11.12',
      port: 8080,
      protocol: 'socks5',
      active: false,
    },
  ];

  beforeEach(() => {
    manager = new SimpleProxyManager();
  });

  describe('proxy management', () => {
    it('should add proxies', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);
      manager.addProxy(mockProxies[1]);

      const proxies = manager.getProxies();
      expect(proxies).toHaveLength(2);
      expect(proxies[0].id).toBe('proxy1');
      expect(proxies[1].id).toBe('proxy2');
    });

    it('should remove proxy by id', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);
      manager.addProxy(mockProxies[1]);

      manager.removeProxy('proxy1');

      const proxies = manager.getProxies();
      expect(proxies).toHaveLength(1);
      expect(proxies[0].id).toBe('proxy2');
    });

    it('should update proxy status', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);

      manager.updateProxyStatus('proxy1', false);

      const proxies = manager.getProxies();
      expect(proxies[0].active).toBe(false);
    });

    it('should get only active proxies', () => {
      const mockProxies = getMockProxies();
      mockProxies.forEach(proxy => manager.addProxy(proxy));

      const activeProxies = manager.getActiveProxies();

      expect(activeProxies).toHaveLength(2);
      expect(activeProxies.every(p => p.active)).toBe(true);
    });
  });

  describe('proxy rotation', () => {
    it('should rotate through proxies', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);
      manager.addProxy(mockProxies[1]);

      const proxy1 = manager.getNextProxy();
      const proxy2 = manager.getNextProxy();
      const proxy3 = manager.getNextProxy();

      expect(proxy1?.id).toBe('proxy1');
      expect(proxy2?.id).toBe('proxy2');
      expect(proxy3?.id).toBe('proxy1'); // Back to first
    });

    it('should skip inactive proxies', () => {
      const mockProxies = getMockProxies();
      mockProxies.forEach(proxy => manager.addProxy(proxy));

      const proxy1 = manager.getNextProxy();
      const proxy2 = manager.getNextProxy();
      const proxy3 = manager.getNextProxy();

      expect(proxy1?.id).toBe('proxy1');
      expect(proxy2?.id).toBe('proxy2');
      expect(proxy3?.id).toBe('proxy1'); // Skips proxy3 (inactive)
    });

    it('should return null when no active proxies', () => {
      const mockProxies = getMockProxies();
      manager.addProxy({ ...mockProxies[0], active: false });

      const proxy = manager.getNextProxy();

      expect(proxy).toBeNull();
    });
  });

  describe('proxy configuration', () => {
    it('should get proxy config for HTTP proxy', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);
      const proxy = manager.getNextProxy();

      const config = manager.getProxyConfig(proxy!);

      expect(config).toEqual({
        protocol: 'http',
        host: '1.2.3.4',
        port: 8080,
        auth: {
          username: 'user1',
          password: 'pass1',
        },
      });
    });

    it('should get proxy config without auth', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[2]);
      manager.updateProxyStatus('proxy3', true); // Make it active
      const proxy = manager.getNextProxy();

      const config = manager.getProxyConfig(proxy!);

      expect(config).toEqual({
        protocol: 'socks5',
        host: '9.10.11.12',
        port: 8080,
      });
    });

    it('should format proxy URL', () => {
      const mockProxies = getMockProxies();
      const url1 = manager.formatProxyUrl(mockProxies[0]);
      expect(url1).toBe('http://user1:pass1@1.2.3.4:8080');

      const url2 = manager.formatProxyUrl(mockProxies[2]);
      expect(url2).toBe('socks5://9.10.11.12:8080');
    });
  });

  describe('proxy validation', () => {
    it('should validate proxy connectivity', async () => {
      const mockProxies = getMockProxies();
      // Mock fetch for validation
      const mockFetch = mock(() => Promise.resolve({ ok: true }));
      global.fetch = mockFetch as any;

      manager.addProxy(mockProxies[0]);

      const isValid = await manager.validateProxy('proxy1');

      expect(mockFetch).toHaveBeenCalled();
      expect(isValid).toBe(true);
    });

    it('should handle validation failure', async () => {
      const mockProxies = getMockProxies();
      const mockFetch = mock(() => Promise.reject(new Error('Connection failed')));
      global.fetch = mockFetch as any;

      manager.addProxy(mockProxies[0]);

      const isValid = await manager.validateProxy('proxy1');

      expect(isValid).toBe(false);
    });

    it('should validate all proxies', async () => {
      const mockProxies = getMockProxies();

      // Mock fetch to return different results for each proxy
      let callCount = 0;
      const mockFetch = mock(() => {
        callCount++;
        // First call succeeds, second fails
        if (callCount === 1) {
          return Promise.resolve({ ok: true });
        } else {
          return Promise.reject(new Error('Failed'));
        }
      });
      global.fetch = mockFetch as any;

      manager.addProxy(mockProxies[0]);
      manager.addProxy(mockProxies[1]);

      const results = await manager.validateAllProxies();

      expect(results['proxy1']).toBe(true);
      expect(results['proxy2']).toBe(false);

      // Should disable failed proxy
      const activeProxies = manager.getActiveProxies();
      expect(activeProxies).toHaveLength(1);
      expect(activeProxies[0].id).toBe('proxy1');
    });
  });

  describe('statistics', () => {
    it('should track proxy statistics', () => {
      const mockProxies = getMockProxies();
      mockProxies.forEach(proxy => manager.addProxy(proxy));

      const stats = manager.getStatistics();

      expect(stats).toEqual({
        total: 3,
        active: 2,
        inactive: 1,
        byProtocol: {
          http: 2,
          socks5: 1,
        },
      });
    });

    it('should clear all proxies', () => {
      const mockProxies = getMockProxies();
      manager.addProxy(mockProxies[0]);
      manager.addProxy(mockProxies[1]);

      manager.clear();

      const proxies = manager.getProxies();
      expect(proxies).toHaveLength(0);
    });
  });
});
