import { Test, type TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException } from '@nestjs/common';
import { BankService } from './bank.service';
import { BankRepository } from './repositories/bank.repository';

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

  const mockRepo = {
    findPaginated: jest.fn(),
    findById: jest.fn(),
    findByName: jest.fn(),
    findAllActive: jest.fn(),
    find: jest.fn(),
    save: jest.fn(),
    update: jest.fn(),
    softDelete: jest.fn(),
    countAccounts: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [BankService, { provide: BankRepository, useValue: mockRepo }],
    }).compile();

    service = module.get(BankService);
  });

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

  describe('findAll', () => {
    it('returns paginated banks', async () => {
      const paginated = {
        items: [{ id: '1', name: 'HDFC' }],
        meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
      };
      mockRepo.findPaginated.mockResolvedValue(paginated);

      await expect(service.findAll()).resolves.toEqual(paginated);
      expect(mockRepo.findPaginated).toHaveBeenCalledTimes(1);
    });
  });

  describe('findById', () => {
    it('returns the bank when found', async () => {
      const item = { id: '1', name: 'HDFC' };
      mockRepo.findById.mockResolvedValue(item);
      await expect(service.findById('1')).resolves.toEqual(item);
    });

    it('throws NotFoundException for a different-tenant (or missing) bank', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.findById('other-tenant-bank')).rejects.toThrow(NotFoundException);
    });
  });

  describe('create', () => {
    it('creates a bank', async () => {
      mockRepo.findByName.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'new-1' });

      const result = await service.create({ name: ' HDFC ' });

      expect(result).toEqual({ id: 'new-1', message: 'Bank created successfully' });
      expect(mockRepo.save).toHaveBeenCalledWith({ name: 'HDFC', is_active: true });
    });

    it('throws ConflictException on duplicate name', async () => {
      mockRepo.findByName.mockResolvedValue({ id: 'existing' });
      await expect(service.create({ name: 'HDFC' })).rejects.toThrow(ConflictException);
      expect(mockRepo.save).not.toHaveBeenCalled();
    });
  });

  describe('update', () => {
    it('blocks deactivate when accounts still reference the bank', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1', name: 'HDFC', is_active: true });
      mockRepo.countAccounts.mockResolvedValue(2);

      await expect(service.update('1', { is_active: false })).rejects.toThrow(ConflictException);
      expect(mockRepo.update).not.toHaveBeenCalled();
    });

    it('updates when no accounts block the change', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1', name: 'HDFC', is_active: true });
      mockRepo.findByName.mockResolvedValue(null);
      mockRepo.update.mockResolvedValue(undefined);

      await expect(service.update('1', { name: 'HDFC Bank' })).resolves.toEqual({
        id: '1',
        message: 'Bank updated successfully',
      });
    });
  });

  describe('remove', () => {
    it('soft-deletes when no accounts exist', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1' });
      mockRepo.countAccounts.mockResolvedValue(0);
      mockRepo.softDelete.mockResolvedValue(undefined);

      await expect(service.remove('1')).resolves.toEqual({
        message: 'Bank deleted successfully',
      });
      expect(mockRepo.softDelete).toHaveBeenCalledWith('1');
    });

    it('blocks delete when accounts still reference the bank', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1' });
      mockRepo.countAccounts.mockResolvedValue(1);

      await expect(service.remove('1')).rejects.toThrow(ConflictException);
      expect(mockRepo.softDelete).not.toHaveBeenCalled();
    });

    it('throws NotFoundException for a different-tenant bank', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.remove('other-tenant-bank')).rejects.toThrow(NotFoundException);
    });
  });
});
