import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { configureTestBed } from '~/testing/unit-test-helper';
import { NvmeofService } from '~/app/shared/api/nvmeof.service';
import { HostService } from './host.service';
import { OrchestratorService } from './orchestrator.service';
import { of, throwError } from 'rxjs';

describe('NvmeofService', () => {
  let service: NvmeofService;
  let httpTesting: HttpTestingController;
  const mockGroupName = 'default';
  const mockNQN = 'nqn.2001-07.com.ceph:1721041732363';
  const mockHostService = {
    checkHostsFactsAvailable: jest.fn(),
    list: jest.fn(),
    getAllHosts: jest.fn()
  };

  const mockOrchService = {
    status: jest.fn()
  };
  const UI_API_PATH = 'ui-api/nvmeof';
  const API_PATH = 'api/nvmeof';

  configureTestBed({
    providers: [
      NvmeofService,
      { provide: HostService, useValue: mockHostService },
      { provide: OrchestratorService, useValue: mockOrchService }
    ],
    imports: [HttpClientTestingModule]
  });

  beforeEach(() => {
    service = TestBed.inject(NvmeofService);
    httpTesting = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpTesting.verify();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('test gateway APIs', () => {
    it('should call listGatewayGroups', () => {
      service.listGatewayGroups().subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      expect(req.request.method).toBe('GET');
    });

    it('should call listGateways', () => {
      service.listGateways().subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/gateway`);
      expect(req.request.method).toBe('GET');
    });

    it('should check if gateway group exists - returns true when group exists', () => {
      const mockGroups = [
        [
          {
            spec: { group: 'default' },
            service_name: 'nvmeof.rbd.default'
          },
          {
            spec: { group: 'test-group' },
            service_name: 'nvmeof.rbd.test-group'
          }
        ]
      ];

      service.exists('default').subscribe((exists: boolean) => {
        expect(exists).toBe(true);
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      expect(req.request.method).toBe('GET');
      req.flush(mockGroups);
    });

    it('should check if gateway group exists - returns false when group does not exist', () => {
      const mockGroups = [
        [
          {
            spec: { group: 'default' },
            service_name: 'nvmeof.rbd.default'
          }
        ]
      ];

      service.exists('non-existent-group').subscribe((exists: boolean) => {
        expect(exists).toBe(false);
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      expect(req.request.method).toBe('GET');
      req.flush(mockGroups);
    });

    it('should check if gateway group exists - returns false on API error', () => {
      service.exists('test-group').subscribe((exists: boolean) => {
        expect(exists).toBe(false);
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      expect(req.request.method).toBe('GET');
      req.error(new ErrorEvent('Network error'));
    });

    it('should get available hosts', () => {
      const mockGroups = [[{ placement: { hosts: ['used-host'] } }]];
      const mockHosts = [
        { hostname: 'used-host', status: 'Available' },
        { hostname: 'free-host', status: 'Available' }
      ];

      mockOrchService.status.mockReturnValue(of({ available: true }));
      mockHostService.checkHostsFactsAvailable.mockReturnValue(true);
      mockHostService.list.mockReturnValue(of(mockHosts));

      service.getAvailableHosts().subscribe((hosts: any[]) => {
        expect(hosts.length).toBe(1);
        expect(hosts[0].hostname).toBe('free-host');
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });

    it('should fetch hosts and groups', () => {
      const mockGroups = [[{ spec: { group: 'group1' } }]];
      const mockHosts = [{ hostname: 'host1', status: '' }];

      mockHostService.getAllHosts.mockReturnValue(of(mockHosts));

      service.fetchHostsAndGroups().subscribe((result: any) => {
        expect(result.groups).toEqual(mockGroups);
        expect(result.hosts[0].status).toBe('Available');
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });
  });

  describe('test subsystems APIs', () => {
    it('should call listSubsystems', () => {
      service.listSubsystems(mockGroupName).subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/subsystem?gw_group=${mockGroupName}`);
      expect(req.request.method).toBe('GET');
    });

    it('should call getSubsystem', () => {
      service.getSubsystem(mockNQN, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('GET');
    });

    it('should call createSubsystem', () => {
      const request = {
        nqn: mockNQN,
        gw_group: mockGroupName,
        dhchap_key: null as string | null
      };
      service.createSubsystem(request).subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/subsystem`);
      expect(req.request.method).toBe('POST');
    });

    it('should call deleteSubsystem', () => {
      service.deleteSubsystem(mockNQN, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('DELETE');
    });
    it('should call isSubsystemPresent', () => {
      spyOn(service, 'getSubsystem').and.returnValue(throwError('test'));
      service.isSubsystemPresent(mockNQN, mockGroupName).subscribe((res: boolean) => {
        expect(res).toBe(false);
      });
    });
  });

  describe('test initiators APIs', () => {
    let request = { host_nqn: '', gw_group: mockGroupName };
    let addRequest = {
      hosts: [] as { dhchap_key: string; host_nqn: string }[],
      allow_all: true,
      gw_group: mockGroupName
    };
    it('should call getInitiators', () => {
      service.getInitiators(mockNQN, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}/host?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('GET');
    });
    it('should call addInitiators', () => {
      service.addSubsystemInitiators(mockNQN, addRequest).subscribe();
      const req = httpTesting.expectOne(`${UI_API_PATH}/subsystem/${mockNQN}/host`);
      expect(req.request.method).toBe('POST');
    });
    it('should call removeInitiators', () => {
      service.removeInitiators(mockNQN, request).subscribe();
      const req = httpTesting.expectOne(
        `${UI_API_PATH}/subsystem/${mockNQN}/host/${request.host_nqn}/${mockGroupName}`
      );
      expect(req.request.method).toBe('DELETE');
    });
  });

  describe('test listener APIs', () => {
    it('it should listListeners', () => {
      service.listListeners(mockNQN, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}/listener?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('GET');
    });
    it('should call createListener', () => {
      const request = {
        gw_group: mockGroupName,
        host_name: 'ceph-node-02',
        traddr: '192.168.100.102',
        trsvcid: 4421
      };
      service.createListener(mockNQN, request).subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/subsystem/${mockNQN}/listener`);
      expect(req.request.method).toBe('POST');
    });
    it('should call deleteListener', () => {
      const request = { host_name: 'ceph-node-02', traddr: '192.168.100.102', trsvcid: '4421' };
      service
        .deleteListener(mockNQN, mockGroupName, request.host_name, request.traddr, request.trsvcid)
        .subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}/listener/${request.host_name}/${request.traddr}?gw_group=${mockGroupName}&trsvcid=${request.trsvcid}&force=true`
      );
      expect(req.request.method).toBe('DELETE');
    });
  });

  describe('test namespace APIs', () => {
    const mockNsid = '1';
    it('should call listNamespaces', () => {
      service.listNamespaces(mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/*/namespace?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('GET');
    });
    it('should call getNamespace', () => {
      service.getNamespace(mockNQN, mockNsid, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}/namespace/${mockNsid}?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('GET');
    });
    it('should call createNamespace', () => {
      const mockNamespaceObj = {
        rbd_image_name: 'nvme_ns_image:12345678',
        rbd_pool: 'rbd',
        rbd_image_size: 1024,
        create_image: true,
        gw_group: mockGroupName
      };
      service.createNamespace(mockNQN, mockNamespaceObj).subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/subsystem/${mockNQN}/namespace`);
      expect(req.request.method).toBe('POST');
    });
    it('should call updateNamespace', () => {
      const request = { rbd_image_size: 1024, gw_group: mockGroupName };
      service.updateNamespace(mockNQN, mockNsid, request).subscribe();
      const req = httpTesting.expectOne(`${API_PATH}/subsystem/${mockNQN}/namespace/${mockNsid}`);
      expect(req.request.method).toBe('PATCH');
    });
    it('should call deleteNamespace', () => {
      service.deleteNamespace(mockNQN, mockNsid, mockGroupName).subscribe();
      const req = httpTesting.expectOne(
        `${API_PATH}/subsystem/${mockNQN}/namespace/${mockNsid}?gw_group=${mockGroupName}`
      );
      expect(req.request.method).toBe('DELETE');
    });

    it('should call addNamespaceInitiators with UI namespace host endpoint', () => {
      const request = {
        subsystem_nqn: mockNQN,
        host_nqn: 'nqn.2014-08.org.nvmexpress:uuid:11111111-1111-1111-1111-111111111111',
        gw_group: mockGroupName
      };
      service.addNamespaceInitiators(mockNsid, request).subscribe();
      const req = httpTesting.expectOne(`${UI_API_PATH}/namespace/${mockNsid}/host`);
      expect(req.request.method).toBe('POST');
      expect(req.request.body).toEqual(request);
    });
  });

  describe('getHostsForGroup', () => {
    const allHosts = [
      { hostname: 'host1', labels: ['nvmeof'], status: '' },
      { hostname: 'host2', labels: ['storage'], status: '' },
      { hostname: 'host3', labels: ['nvmeof', 'storage'], status: '' }
    ];

    it('should filter hosts by direct host placement', (done) => {
      const mockGroups = [
        [
          {
            spec: { group: 'default' },
            placement: { hosts: ['host1', 'host3'], label: [] as string[] }
          }
        ]
      ];
      mockHostService.getAllHosts.mockReturnValue(of(allHosts));

      service.getHostsForGroup('default').subscribe((hosts: any[]) => {
        expect(hosts.length).toBe(2);
        expect(hosts.map((h: any) => h.hostname)).toEqual(['host1', 'host3']);
        done();
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });

    it('should filter hosts by string label placement', (done) => {
      const mockGroups = [
        [{ spec: { group: 'default' }, placement: { hosts: [] as string[], label: 'nvmeof' } }]
      ];
      mockHostService.getAllHosts.mockReturnValue(of(allHosts));

      service.getHostsForGroup('default').subscribe((hosts: any[]) => {
        expect(hosts.length).toBe(2);
        expect(hosts.map((h: any) => h.hostname)).toEqual(['host1', 'host3']);
        done();
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });

    it('should return empty array when group not found', (done) => {
      const mockGroups = [
        [{ spec: { group: 'other' }, placement: { hosts: ['host1'], label: [] as string[] } }]
      ];
      mockHostService.getAllHosts.mockReturnValue(of(allHosts));

      service.getHostsForGroup('non-existent').subscribe((hosts: any[]) => {
        expect(hosts.length).toBe(0);
        done();
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });

    it('should return empty array when placement has no hosts or labels', (done) => {
      const mockGroups = [
        [
          {
            spec: { group: 'default' },
            placement: { hosts: [] as string[], label: [] as string[] }
          }
        ]
      ];
      mockHostService.getAllHosts.mockReturnValue(of(allHosts));

      service.getHostsForGroup('default').subscribe((hosts: any[]) => {
        expect(hosts.length).toBe(0);
        done();
      });

      const req = httpTesting.expectOne(`${API_PATH}/gateway/group`);
      req.flush(mockGroups);
    });
  });

  describe('createListeners', () => {
    it('should call createListener for each listener in the array', () => {
      const listeners = [
        { content: 'ceph-node-01', addr: '192.168.1.1' },
        { content: 'ceph-node-02', addr: '192.168.1.2' }
      ];

      service.createListeners('nqn.test', 'default', listeners).subscribe();

      const reqs = httpTesting.match(`${API_PATH}/subsystem/nqn.test/listener`);
      expect(reqs.length).toBe(2);
      expect(reqs[0].request.method).toBe('POST');
      expect(reqs[0].request.body).toEqual({
        gw_group: 'default',
        host_name: 'ceph-node-01',
        traddr: '192.168.1.1',
        trsvcid: 4420
      });
      expect(reqs[1].request.body).toEqual({
        gw_group: 'default',
        host_name: 'ceph-node-02',
        traddr: '192.168.1.2',
        trsvcid: 4420
      });

      reqs.forEach((req) => req.flush({}, { status: 200, statusText: 'OK' }));
    });

    it('should call createListener for a single listener', () => {
      const listeners = [{ content: 'ceph-node-01', addr: '192.168.1.1' }];

      service.createListeners('nqn.test', 'group1', listeners).subscribe();

      const reqs = httpTesting.match(`${API_PATH}/subsystem/nqn.test/listener`);
      expect(reqs.length).toBe(1);
      expect(reqs[0].request.body).toEqual({
        gw_group: 'group1',
        host_name: 'ceph-node-01',
        traddr: '192.168.1.1',
        trsvcid: 4420
      });

      reqs[0].flush({}, { status: 200, statusText: 'OK' });
    });
  });
});
