/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SubscriptionsService } from 'src/subscriptions/subscriptions.service';
import { Subscription } from 'src/subscriptions/entities/subscription.entity';
import { XuiService } from 'src/xui/xui.service';
import { CreateSubscriptionDto } from 'src/subscriptions/dto/create-subscription.dto';

describe('SubscriptionsService', () => {
  let service: SubscriptionsService;
  let subRepo: Repository<Subscription>;
  let xuiService: XuiService;

  const mockSubRepo = {
    find: jest.fn(),
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
    delete: jest.fn(),
    remove: jest.fn(),
  };

  const mockXuiService = {
    deleteInbound: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        SubscriptionsService,
        {
          provide: getRepositoryToken(Subscription),
          useValue: mockSubRepo,
        },
        {
          provide: XuiService,
          useValue: mockXuiService,
        },
      ],
    }).compile();

    service = module.get<SubscriptionsService>(SubscriptionsService);
    subRepo = module.get<Repository<Subscription>>(
      getRepositoryToken(Subscription),
    );
    xuiService = module.get<XuiService>(XuiService);
  });

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

  describe('findAll', () => {
    it('должен вернуть все подписки с relations и сортировкой', async () => {
      const mockSubs: Subscription[] = [
        {
          id: '1',
          name: 'Sub 1',
          uuid: 'uuid-1',
          isEnabled: true,
          isAutoRotationEnabled: true,
          inboundsConfig: [],
          inbounds: [],
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: '2',
          name: 'Sub 2',
          uuid: 'uuid-2',
          isEnabled: true,
          isAutoRotationEnabled: false,
          inboundsConfig: [],
          inbounds: [],
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockSubRepo.find.mockResolvedValue(mockSubs);

      const result = await service.findAll();

      expect(result).toEqual(mockSubs);
      expect(subRepo.find).toHaveBeenCalledWith({
        relations: ['inbounds'],
        order: { createdAt: 'DESC' },
      });
    });

    it('должен вернуть пустой массив, если подписок нет', async () => {
      mockSubRepo.find.mockResolvedValue([]);

      const result = await service.findAll();

      expect(result).toEqual([]);
    });
  });

  describe('create', () => {
    const createDto: CreateSubscriptionDto = {
      name: 'Test Subscription',
      inboundsConfig: [
        { type: 'vless-tcp-reality', port: 443, sni: 'example.com' },
      ],
      isAutoRotationEnabled: true,
    };

    it('должен создать подписку с UUID и настройками по умолчанию', async () => {
      const mockSubscription = {
        ...createDto,
        id: 'test-id',
        uuid: 'generated-uuid',
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockSubRepo.create.mockReturnValue(mockSubscription);
      mockSubRepo.save.mockResolvedValue(mockSubscription);

      const result = await service.create(createDto);

      expect(subRepo.create).toHaveBeenCalledWith({
        name: 'Test Subscription',
        uuid: expect.any(String),
        inboundsConfig: createDto.inboundsConfig,
        isAutoRotationEnabled: true,
      });
      expect(subRepo.save).toHaveBeenCalledWith(mockSubscription);
      expect(result).toEqual(mockSubscription);
    });

    it('должен использовать inboundsConfig по умолчанию [], если не передан', async () => {
      const dtoWithoutConfig: CreateSubscriptionDto = {
        name: 'Test',
        isAutoRotationEnabled: false,
      };

      mockSubRepo.create.mockReturnValue({ ...dtoWithoutConfig, uuid: 'uuid' });
      mockSubRepo.save.mockResolvedValue({ ...dtoWithoutConfig, uuid: 'uuid' });

      await service.create(dtoWithoutConfig);

      expect(subRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          inboundsConfig: [],
        }),
      );
    });

    it('должен использовать isAutoRotationEnabled=true по умолчанию', async () => {
      const dtoWithoutRotation: CreateSubscriptionDto = {
        name: 'Test',
        inboundsConfig: [],
      };

      mockSubRepo.create.mockReturnValue({
        ...dtoWithoutRotation,
        uuid: 'uuid',
      });
      mockSubRepo.save.mockResolvedValue({
        ...dtoWithoutRotation,
        uuid: 'uuid',
      });

      await service.create(dtoWithoutRotation);

      expect(subRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          isAutoRotationEnabled: true,
        }),
      );
    });
  });

  describe('update', () => {
    const existingSub: Subscription = {
      id: 'test-id',
      name: 'Old Name',
      uuid: 'uuid',
      isEnabled: true,
      isAutoRotationEnabled: true,
      inboundsConfig: [],
      inbounds: [],
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    it('должен обновить имя подписки', async () => {
      mockSubRepo.findOne.mockResolvedValue(existingSub);
      mockSubRepo.save.mockResolvedValue({ ...existingSub, name: 'New Name' });

      const result = await service.update('test-id', { name: 'New Name' });

      expect(subRepo.findOne).toHaveBeenCalledWith({
        where: { id: 'test-id' },
        relations: ['inbounds'],
      });
      expect(subRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({ name: 'New Name' }),
      );
      expect(result).toEqual({ ...existingSub, name: 'New Name' });
    });

    it('должен обновить isAutoRotationEnabled', async () => {
      mockSubRepo.findOne.mockResolvedValue(existingSub);
      mockSubRepo.save.mockResolvedValue({
        ...existingSub,
        isAutoRotationEnabled: false,
      });

      const result = await service.update('test-id', {
        isAutoRotationEnabled: false,
      });

      expect(subRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({ isAutoRotationEnabled: false }),
      );
      expect(result).toEqual({ ...existingSub, isAutoRotationEnabled: false });
    });

    it('должен обновить inboundsConfig', async () => {
      const newConfig = [{ type: 'vmess-tcp', port: 8080 }];
      mockSubRepo.findOne.mockResolvedValue(existingSub);
      mockSubRepo.save.mockResolvedValue({
        ...existingSub,
        inboundsConfig: newConfig,
      });

      await service.update('test-id', { inboundsConfig: newConfig });

      expect(subRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({ inboundsConfig: newConfig }),
      );
    });

    it('НЕ должен обновлять имя на пустую строку (защита от очистки)', async () => {
      mockSubRepo.findOne.mockResolvedValue(existingSub);
      mockSubRepo.save.mockResolvedValue(existingSub);

      await service.update('test-id', { name: '' });

      expect(subRepo.save).not.toHaveBeenCalledWith(
        expect.objectContaining({ name: '' }),
      );
      expect(subRepo.save).toHaveBeenCalledWith(existingSub);
    });

    it('НЕ должен обновлять имя с пробелами (защита от очистки)', async () => {
      mockSubRepo.findOne.mockResolvedValue(existingSub);
      mockSubRepo.save.mockResolvedValue(existingSub);

      await service.update('test-id', { name: '   ' });

      expect(subRepo.save).toHaveBeenCalledWith(existingSub);
    });

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

      const result = await service.update('non-existent-id', { name: 'New' });

      expect(result).toBeNull();
      expect(subRepo.save).not.toHaveBeenCalled();
    });

    it('должен обновить только isAutoRotationEnabled, не трогая имя', async () => {
      const freshSub: Subscription = {
        ...existingSub,
        name: 'Old Name',
        inboundsConfig: [],
      };
      mockSubRepo.findOne.mockResolvedValue(freshSub);
      mockSubRepo.save.mockImplementation((sub) => Promise.resolve(sub));

      await service.update('test-id', { isAutoRotationEnabled: false });

      expect(subRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({
          name: 'Old Name',
          isAutoRotationEnabled: false,
        }),
      );
    });
  });

  describe('remove', () => {
    const subWithInbounds: Subscription = {
      id: 'test-id',
      name: 'Test',
      uuid: 'uuid',
      isEnabled: true,
      isAutoRotationEnabled: true,
      inboundsConfig: [],
      inbounds: [
        {
          id: '1',
          xuiId: 101,
          port: 443,
          protocol: 'vless',
          remark: 'test',
          link: 'link',
          subscription: null as any,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: '2',
          xuiId: 102,
          port: 8443,
          protocol: 'vmess',
          remark: 'test2',
          link: 'link2',
          subscription: null as any,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ],
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    it('должен удалить инбаунды из 3x-ui перед удалением подписки', async () => {
      mockSubRepo.findOne.mockResolvedValue(subWithInbounds);
      mockSubRepo.remove.mockResolvedValue(undefined);

      await service.remove('test-id');

      expect(xuiService.deleteInbound).toHaveBeenCalledWith(101);
      expect(xuiService.deleteInbound).toHaveBeenCalledWith(102);
      expect(subRepo.remove).toHaveBeenCalledWith(subWithInbounds);
    });

    it('должен удалить подписку без инбаундов', async () => {
      const subWithoutInbounds: Subscription = {
        ...subWithInbounds,
        inbounds: [],
      };

      mockSubRepo.findOne.mockResolvedValue(subWithoutInbounds);
      mockSubRepo.remove.mockResolvedValue(undefined);

      await service.remove('test-id');

      expect(xuiService.deleteInbound).not.toHaveBeenCalled();
      expect(subRepo.remove).toHaveBeenCalledWith(subWithoutInbounds);
    });

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

      const result = await service.remove('non-existent-id');

      expect(result).toBeUndefined();
      expect(subRepo.remove).not.toHaveBeenCalled();
      expect(xuiService.deleteInbound).not.toHaveBeenCalled();
    });
  });
});
