import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException, ForbiddenException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { ResidentsService } from './residents.service';
import { PrismaService } from '../database/prisma.service';
import { AuditService } from '../audit/audit.service';
import { RequestUser } from '../common/request-user';

describe('ResidentsService', () => {
  let service: ResidentsService;
  let prisma: PrismaService;
  let audit: AuditService;

  const mockPrisma = {
    resident: {
      findMany: jest.fn(),
      findFirst: jest.fn()
    },
    user: {
      create: jest.fn()
    },
    estate: {
      findUnique: jest.fn()
    },
    $transaction: jest.fn()
  };

  const mockAudit = {
    record: jest.fn()
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ResidentsService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: AuditService, useValue: mockAudit }
      ]
    }).compile();

    service = module.get<ResidentsService>(ResidentsService);
    prisma = module.get<PrismaService>(PrismaService);
    audit = module.get<AuditService>(AuditService);

    jest.clearAllMocks();
    mockPrisma.estate.findUnique.mockResolvedValue({
      subscriptionPlan: { limits: { residents: 100 } },
      _count: { residents: 0 }
    });
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findAll', () => {
    it('should return residents for estate admin', () => {
      const user: RequestUser = {
        id: 'user-1',
        role: 'ESTATE_ADMIN',
        estateId: 'estate-1'
      };

      const mockResidents = [
        {
          id: 'resident-1',
          estateId: 'estate-1',
          name: 'Ada Okafor',
          unit: 'A12',
          phone: '+2348000000003'
        }
      ];

      mockPrisma.resident.findMany.mockReturnValue(mockResidents);

      const result = service.findAll(user, 'estate-1');

      expect(result).toEqual(mockResidents);
      expect(prisma.resident.findMany).toHaveBeenCalled();
    });

    it('should throw error for different estate scope', () => {
      const user: RequestUser = {
        id: 'user-1',
        role: 'ESTATE_ADMIN',
        estateId: 'estate-1'
      };

      expect(() => service.findAll(user, 'estate-2')).toThrow(ForbiddenException);
    });
  });

  describe('create', () => {
    it('should create resident with transaction', async () => {
      const user: RequestUser = {
        id: 'user-1',
        role: 'ESTATE_ADMIN',
        estateId: 'estate-1'
      };

      const dto = {
        name: 'Ada Okafor',
        phone: '+2348000000003',
        unit: 'A12',
        estateId: 'estate-1'
      };

      const mockNewResident = {
        id: 'resident-1',
        estateId: 'estate-1',
        userId: 'user-new',
        name: 'Ada Okafor',
        phone: '+2348000000003',
        unit: 'A12',
        status: 'ACTIVE',
        createdAt: new Date(),
        updatedAt: new Date()
      };

      mockPrisma.$transaction.mockResolvedValue(mockNewResident);
      mockAudit.record.mockResolvedValue(undefined);

      const result = await service.create(dto, user);

      expect(result).toEqual(mockNewResident);
      expect(audit.record).toHaveBeenCalledWith(
        expect.objectContaining({
          action: 'RESIDENT_CREATED',
          estateId: 'estate-1'
        })
      );
    });

    it('should throw error when accessing different estate', async () => {
      const user: RequestUser = {
        id: 'user-1',
        role: 'ESTATE_ADMIN',
        estateId: 'estate-1'
      };

      const dto = {
        name: 'Ada Okafor',
        phone: '+2348000000003',
        unit: 'A12',
        estateId: 'estate-2'
      };

      await expect(service.create(dto, user)).rejects.toThrow(ForbiddenException);
    });

    it('should throw conflict when resident phone already exists', async () => {
      const user: RequestUser = {
        id: 'user-1',
        role: 'ESTATE_ADMIN',
        estateId: 'estate-1'
      };

      const dto = {
        name: 'Ada Okafor',
        phone: '+2348000000003',
        unit: 'A12',
        estateId: 'estate-1'
      };

      mockPrisma.$transaction.mockRejectedValue(
        new Prisma.PrismaClientKnownRequestError('Unique constraint failed on the fields: (`phone`)', {
          code: 'P2002',
          clientVersion: 'test',
          meta: { target: ['phone'] }
        })
      );

      const result = service.create(dto, user);

      await expect(result).rejects.toThrow(ConflictException);
      await expect(result).rejects.toThrow('A resident with this phone number already exists');
    });
  });
});
