import { Repository } from 'typeorm';
import { NodesService } from 'src/nodes/nodes.service';
import { Node } from 'src/nodes/entities/node.entity';
import { Subscription } from 'src/subscriptions/entities/subscription.entity';
import { Tunnel } from 'src/tunnels/entities/tunnel.entity';
import { Inbound } from 'src/inbounds/entities/inbound.entity';
import { XuiService } from 'src/xui/xui.service';

describe('NodesService', () => {
  const createNodeRepo = (getOne: jest.Mock) => ({
    createQueryBuilder: jest.fn(() => ({
      addSelect: jest.fn().mockReturnThis(),
      where: jest.fn().mockReturnThis(),
      getOne,
    })),
    count: jest.fn(),
    remove: jest.fn(),
    findOne: jest.fn(),
    save: jest.fn(),
  });

  const createService = (nodeRepo: ReturnType<typeof createNodeRepo>) => {
    const subscriptionsRepo = {
      createQueryBuilder: jest.fn(() => ({
        delete: jest.fn().mockReturnThis(),
        execute: jest.fn().mockResolvedValue({}),
      })),
      find: jest.fn().mockResolvedValue([]),
      save: jest.fn(),
    };
    const tunnelsRepo = {
      createQueryBuilder: jest.fn(() => ({
        delete: jest.fn().mockReturnThis(),
        execute: jest.fn().mockResolvedValue({}),
      })),
      delete: jest.fn(),
    };
    const inboundsRepo = {
      find: jest.fn().mockResolvedValue([]),
      delete: jest.fn(),
    };
    const xuiService = {
      deleteInbound: jest.fn().mockResolvedValue(true),
    };

    return {
      service: new NodesService(
        nodeRepo as unknown as Repository<Node>,
        subscriptionsRepo as unknown as Repository<Subscription>,
        tunnelsRepo as unknown as Repository<Tunnel>,
        inboundsRepo as unknown as Repository<Inbound>,
        xuiService as unknown as XuiService,
      ),
      subscriptionsRepo,
      tunnelsRepo,
      inboundsRepo,
      xuiService,
    };
  };

  it('deletes the main node and makes the next node main', async () => {
    const mainNode = { id: 'main', isMain: true } as Node;
    const nextNode = { id: 'next', isMain: false } as Node;
    const getOne = jest
      .fn()
      .mockResolvedValueOnce(mainNode)
      .mockResolvedValueOnce(null);
    const nodeRepo = createNodeRepo(getOne);
    nodeRepo.count.mockResolvedValue(2);
    nodeRepo.findOne.mockResolvedValue(nextNode);
    nodeRepo.save.mockResolvedValue(nextNode);
    const { service } = createService(nodeRepo);

    const result = await service.remove('main');

    expect(result).toEqual({ success: true });
    expect(nodeRepo.remove).toHaveBeenCalledWith(mainNode);
    expect(nodeRepo.findOne).toHaveBeenCalledWith({
      where: {},
      order: { createdAt: 'DESC' },
    });
    expect(nextNode.isMain).toBe(true);
    expect(nodeRepo.save).toHaveBeenCalledWith(nextNode);
  });

  it('uses node credentials when deleting node inbounds', async () => {
    const mainNode = { id: 'main', isMain: true } as Node;
    const getOne = jest
      .fn()
      .mockResolvedValueOnce(mainNode)
      .mockResolvedValueOnce(null);
    const nodeRepo = createNodeRepo(getOne);
    nodeRepo.count.mockResolvedValue(1);
    nodeRepo.findOne.mockResolvedValue(null);
    const { service, inboundsRepo, xuiService } = createService(nodeRepo);
    inboundsRepo.find.mockResolvedValue([{ id: 1, xuiId: 101, nodeId: 'main' }]);

    await service.remove('main');

    expect(xuiService.deleteInbound).toHaveBeenCalledWith(101, mainNode);
  });
});
