/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */

import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { HttpException } from '@nestjs/common';
import { ClientController } from 'src/client/client.controller';
import { Subscription } from 'src/subscriptions/entities/subscription.entity';
import { Tunnel } from 'src/tunnels/entities/tunnel.entity';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import * as QRCode from 'qrcode';

jest.mock('qrcode', () => ({
  toDataURL: jest.fn(),
}));

jest.mock('src/client/templates/subscription.template', () => ({
  generateSubscriptionHtmlWithQr: jest.fn(
    () => '<html>Subscription Page</html>',
  ),
}));

describe('ClientController', () => {
  let controller: ClientController;
  let _subRepo: Repository<Subscription>;
  let _tunnelRepo: Repository<Tunnel>;
  let cacheManager: any;

  const mockSubRepo = {
    findOne: jest.fn(),
  };

  const mockTunnelRepo = {
    findOne: jest.fn(),
  };

  const mockCacheManager = {
    get: jest.fn(),
    set: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ClientController],
      providers: [
        {
          provide: getRepositoryToken(Subscription),
          useValue: mockSubRepo,
        },
        {
          provide: getRepositoryToken(Tunnel),
          useValue: mockTunnelRepo,
        },
        {
          provide: CACHE_MANAGER,
          useValue: mockCacheManager,
        },
      ],
    }).compile();

    controller = module.get<ClientController>(ClientController);
    _subRepo = module.get<Repository<Subscription>>(
      getRepositoryToken(Subscription),
    );
    _tunnelRepo = module.get<Repository<Tunnel>>(getRepositoryToken(Tunnel));
    cacheManager = module.get(CACHE_MANAGER);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('getSubscription', () => {
    const mockSubscription = {
      uuid: 'test-uuid',
      name: 'Test Subscription',
      isEnabled: true,
      inbounds: [
        { id: 1, link: 'vless://abc123@192.168.1.1:443', protocol: 'vless' },
        { id: 2, link: 'vmess://xyz789', protocol: 'vmess' },
      ],
    };

    const mockRequest = {
      headers: {},
      protocol: 'https',
      get: jest.fn().mockReturnValue('example.com'),
    } as any;

    const mockResponse = {
      setHeader: jest.fn(),
      send: jest.fn(),
    } as any;

    it('должен вернуть base64 подписку для не-браузера', async () => {
      mockRequest.headers['user-agent'] = 'curl/7.68.0';
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);
      (QRCode.toDataURL as jest.Mock).mockResolvedValue(
        'data:image/png;base64,qr',
      );

      await controller.getSubscription('test-uuid', mockRequest, mockResponse);

      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'Content-Type',
        'text/plain; charset=utf-8',
      );
      expect(mockResponse.send).toHaveBeenCalledWith(expect.any(String));
    });

    it('должен вернуть HTML с QR для браузера', async () => {
      mockRequest.headers['user-agent'] = 'Mozilla/5.0 Chrome/120.0';
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);
      (QRCode.toDataURL as jest.Mock).mockResolvedValue(
        'data:image/png;base64,qr',
      );
      mockCacheManager.get.mockResolvedValue(null);

      await controller.getSubscription('test-uuid', mockRequest, mockResponse);

      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'Content-Type',
        'text/html',
      );
      expect(mockResponse.send).toHaveBeenCalledWith(
        '<html>Subscription Page</html>',
      );
    });

    it('должен загрузить QR из кэша', async () => {
      mockRequest.headers['user-agent'] = 'Mozilla/5.0 Chrome/120.0';
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);
      mockCacheManager.get.mockResolvedValue('data:image/png;base64,cached-qr');

      await controller.getSubscription('test-uuid', mockRequest, mockResponse);

      expect(cacheManager.get).toHaveBeenCalledWith('qr_test-uuid');
      expect(QRCode.toDataURL).not.toHaveBeenCalled();
    });

    it('должен бросить 404, если подписка не найдена', async () => {
      mockSubRepo.findOne.mockResolvedValue(null);

      await expect(
        controller.getSubscription('non-existent', mockRequest, mockResponse),
      ).rejects.toThrow(HttpException);

      await expect(
        controller.getSubscription('non-existent', mockRequest, mockResponse),
      ).rejects.toThrow('Subscription not found');
    });

    it('должен бросить 404, если подписка отключена', async () => {
      const disabledSub = { ...mockSubscription, isEnabled: false };
      mockSubRepo.findOne.mockResolvedValue(disabledSub);

      await expect(
        controller.getSubscription('test-uuid', mockRequest, mockResponse),
      ).rejects.toThrow(HttpException);
    });

    it('должен обработать подписку без инбаундов', async () => {
      const subWithoutInbounds = { ...mockSubscription, inbounds: [] };
      mockRequest.headers['user-agent'] = 'curl/7.68.0';
      mockSubRepo.findOne.mockResolvedValue(subWithoutInbounds);

      await controller.getSubscription('test-uuid', mockRequest, mockResponse);

      expect(mockResponse.send).toHaveBeenCalledWith('');
    });
  });

  describe('getRelaySubscription', () => {
    const mockTunnel = {
      id: 1,
      ip: '192.168.1.100',
      domain: 'relay.example.com',
    };

    const mockSubscription = {
      uuid: 'test-uuid',
      name: 'Test Subscription',
      isEnabled: true,
      inbounds: [
        { id: 1, link: 'vless://abc123@192.168.1.1:443', protocol: 'vless' },
        { id: 2, link: 'vmess://xyz789', protocol: 'vmess' },
        { id: 3, link: 'custom-link', protocol: 'custom' },
      ],
    };

    const mockRequest = {
      headers: {},
      protocol: 'https',
      get: jest.fn().mockReturnValue('example.com'),
    } as any;

    const mockResponse = {
      setHeader: jest.fn(),
      send: jest.fn(),
      status: jest.fn().mockReturnThis(),
    } as any;

    it('должен вернуть 404, если туннель не найден', async () => {
      mockTunnelRepo.findOne.mockResolvedValue(null);

      await controller.getRelaySubscription(
        'test-uuid',
        '999',
        'base64',
        mockRequest,
        mockResponse,
      );

      expect(mockResponse.status).toHaveBeenCalledWith(404);
      expect(mockResponse.send).toHaveBeenCalledWith('Relay server not found');
    });

    it('должен вернуть base64 подписку для не-браузера с relay', async () => {
      mockRequest.headers['user-agent'] = 'curl/7.68.0';
      mockTunnelRepo.findOne.mockResolvedValue(mockTunnel);
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);

      await controller.getRelaySubscription(
        'test-uuid',
        '1',
        'base64',
        mockRequest,
        mockResponse,
      );

      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'Content-Type',
        'text/plain; charset=utf-8',
      );
      expect(mockResponse.send).toHaveBeenCalled();
    });

    it('должен вернуть HTML с QR для браузера с relay', async () => {
      mockRequest.headers['user-agent'] = 'Mozilla/5.0 Chrome/120.0';
      mockTunnelRepo.findOne.mockResolvedValue(mockTunnel);
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);
      mockCacheManager.get.mockResolvedValue(null);
      (QRCode.toDataURL as jest.Mock).mockResolvedValue(
        'data:image/png;base64,qr',
      );

      await controller.getRelaySubscription(
        'test-uuid',
        '1',
        'base64',
        mockRequest,
        mockResponse,
      );

      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'Content-Type',
        'text/html',
      );
      expect(mockResponse.send).toHaveBeenCalledWith(
        '<html>Subscription Page</html>',
      );
    });

    it('должен бросить 404, если подписка не найдена', async () => {
      mockTunnelRepo.findOne.mockResolvedValue(mockTunnel);
      mockSubRepo.findOne.mockResolvedValue(null);

      await expect(
        controller.getRelaySubscription(
          'non-existent',
          '1',
          'base64',
          mockRequest,
          mockResponse,
        ),
      ).rejects.toThrow(HttpException);
    });

    it('должен использовать IP туннеля, если домен не указан', async () => {
      const tunnelWithoutDomain = { ...mockTunnel, domain: null };
      mockRequest.headers['user-agent'] = 'curl/7.68.0';
      mockTunnelRepo.findOne.mockResolvedValue(tunnelWithoutDomain);
      mockSubRepo.findOne.mockResolvedValue(mockSubscription);

      await controller.getRelaySubscription(
        'test-uuid',
        '1',
        'base64',
        mockRequest,
        mockResponse,
      );

      expect(mockResponse.send).toHaveBeenCalled();
    });
  });

  describe('patchLink', () => {
    it('должен обновить хост в vmess ссылке', () => {
      const vmessLink =
        'vmess://' +
        Buffer.from(
          JSON.stringify({ add: 'old-host.com', port: '443' }),
        ).toString('base64');

      // Приватный метод, тестируем через controller
      const result = (controller as any).patchLink(vmessLink, 'new-host.com');

      expect(result).toContain('vmess://');
    });

    it('должен обновить хост в vless ссылке', () => {
      const vlessLink = 'vless://abc@old-host.com:443';

      const result = (controller as any).patchLink(vlessLink, 'new-host.com');

      expect(result).toBe('vless://abc@new-host.com:443');
    });

    it('должен обновить хост в trojan ссылке', () => {
      const trojanLink = 'trojan://pass@old-host.com:443';

      const result = (controller as any).patchLink(trojanLink, 'new-host.com');

      expect(result).toBe('trojan://pass@new-host.com:443');
    });

    it('должен обновить хост в hy2 ссылке', () => {
      const hy2Link = 'hy2://pass@old-host.com:443';

      const result = (controller as any).patchLink(hy2Link, 'new-host.com');

      expect(result).toBe('hy2://pass@new-host.com:443');
    });

    it('должен вернуть ссылку без изменений для неизвестного протокола', () => {
      const unknownLink = 'unknown://abc@host.com:443';

      const result = (controller as any).patchLink(unknownLink, 'new-host.com');

      expect(result).toBe(unknownLink);
    });

    it('должен вернуть vmess ссылку без изменений при ошибке парсинга', () => {
      const invalidVmssLink = 'vmess://invalid-base64!@#';

      const result = (controller as any).patchLink(
        invalidVmssLink,
        'new-host.com',
      );

      expect(result).toBe(invalidVmssLink);
    });
  });
});
