import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException } from '@nestjs/common';
import { getRepositoryToken } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { TenantService } from './tenant.service';
import { TenantEntity } from '../../entities/tenant.entity';
import { UserEntity } from '../../entities/user.entity';
import { RoleEntity } from '../../entities/role.entity';
import { RolePermissionEntity } from '../../entities/role-permission.entity';

jest.mock('bcrypt');

describe('TenantService', () => {
  let service: TenantService;

  const mockTenantRepo = {
    findOne: jest.fn(),
    find: jest.fn(),
    manager: {
      transaction: jest.fn(),
    },
  };

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

  const mockRoleRepo = {};
  const mockRolePermRepo = {};

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TenantService,
        { provide: getRepositoryToken(TenantEntity), useValue: mockTenantRepo },
        { provide: getRepositoryToken(UserEntity), useValue: mockUserRepo },
        { provide: getRepositoryToken(RoleEntity), useValue: mockRoleRepo },
        { provide: getRepositoryToken(RolePermissionEntity), useValue: mockRolePermRepo },
      ],
    }).compile();

    service = module.get<TenantService>(TenantService);
  });

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

  describe('onboard', () => {
    const onboardDto = {
      tenant_name: 'Test DMC',
      subdomain: 'testdmc',
      admin_name: 'Admin User',
      admin_email: 'admin@testdmc.com',
      admin_password: 'Password123',
    };

    it('should throw ConflictException when subdomain already taken', async () => {
      mockTenantRepo.findOne.mockResolvedValue({ id: 'existing' });

      await expect(service.onboard(onboardDto as any)).rejects.toThrow(ConflictException);
    });

    it('should throw ConflictException when admin email already registered', async () => {
      mockTenantRepo.findOne.mockResolvedValue(null);
      mockUserRepo.findOne.mockResolvedValue({ id: 'existing-user' });

      await expect(service.onboard(onboardDto as any)).rejects.toThrow(ConflictException);
    });

    it('should run full onboarding in a transaction', async () => {
      mockTenantRepo.findOne.mockResolvedValue(null);
      mockUserRepo.findOne.mockResolvedValue(null);
      (bcrypt.hash as jest.Mock).mockResolvedValue('hashed-pw');

      const mockManager = {
        create: jest.fn().mockImplementation((_entity, data) => data),
        save: jest.fn().mockImplementation((data) => ({
          ...data,
          id: `mock-${Math.random().toString(36).slice(2, 8)}`,
        })),
      };
      mockTenantRepo.manager.transaction.mockImplementation(async (cb: any) => cb(mockManager));

      const result = await service.onboard(onboardDto as any);

      expect(result).toHaveProperty('tenant');
      expect(result).toHaveProperty('admin');
      expect(result).toHaveProperty('roles_seeded');
      expect(result.message).toBe('Tenant onboarded successfully');
      expect(mockTenantRepo.manager.transaction).toHaveBeenCalledTimes(1);
    });
  });

  describe('findAll', () => {
    it('should return all active tenants', async () => {
      const tenants = [{ id: '1', name: 'Test DMC', is_active: true }];
      mockTenantRepo.find.mockResolvedValue(tenants);

      const result = await service.findAll();

      expect(result).toEqual(tenants);
      expect(mockTenantRepo.find).toHaveBeenCalledWith({
        where: { is_active: true },
        order: { created_at: 'DESC' },
      });
    });

    it('should return empty array when no tenants exist', async () => {
      mockTenantRepo.find.mockResolvedValue([]);
      const result = await service.findAll();
      expect(result).toEqual([]);
    });
  });
});
