import { Test, type TestingModule } from '@nestjs/testing';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { BankAccountService } from './bank-account.service';
import { BankAccountRepository } from './repositories/bank-account.repository';
import { BankRepository } from '../bank/repositories/bank.repository';

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

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

  const mockBankRepo = {
    findById: jest.fn(),
  };

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

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

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

  describe('findById', () => {
    it('returns the account when found', async () => {
      const item = { id: 'a1', account_name: 'Ops', bank_id: 'b1' };
      mockRepo.findByIdWithBank.mockResolvedValue(item);
      await expect(service.findById('a1')).resolves.toEqual(item);
    });

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

  describe('requireActive', () => {
    it('returns the account when it and its bank are active', async () => {
      const item = {
        id: 'a1',
        is_active: true,
        bank: { id: 'b1', is_active: true, is_deleted: false },
      };
      mockRepo.findByIdWithBank.mockResolvedValue(item);
      await expect(service.requireActive('a1')).resolves.toEqual(item);
    });

    it('throws when the account is inactive', async () => {
      mockRepo.findByIdWithBank.mockResolvedValue({
        id: 'a1',
        is_active: false,
        bank: { id: 'b1', is_active: true, is_deleted: false },
      });
      await expect(service.requireActive('a1')).rejects.toThrow(BadRequestException);
    });

    it('throws when the bank is inactive', async () => {
      mockRepo.findByIdWithBank.mockResolvedValue({
        id: 'a1',
        is_active: true,
        bank: { id: 'b1', is_active: false, is_deleted: false },
      });
      await expect(service.requireActive('a1')).rejects.toThrow(BadRequestException);
    });
  });

  describe('create', () => {
    const dto = {
      bank_id: 'b1',
      account_name: 'Ops Current',
    };

    it('creates when the bank exists and is active', async () => {
      mockBankRepo.findById.mockResolvedValue({ id: 'b1', is_active: true });
      mockRepo.save.mockResolvedValue({ id: 'a1' });

      await expect(service.create(dto)).resolves.toEqual({
        id: 'a1',
        message: 'Bank account created successfully',
      });
      expect(mockRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({
          bank_id: 'b1',
          account_name: 'Ops Current',
          is_active: true,
        }),
      );
    });

    it('throws NotFoundException when the bank is missing (cross-tenant or deleted)', async () => {
      mockBankRepo.findById.mockResolvedValue(null);
      await expect(service.create(dto)).rejects.toThrow(NotFoundException);
      expect(mockRepo.save).not.toHaveBeenCalled();
    });

    it('throws BadRequestException when the bank is inactive', async () => {
      mockBankRepo.findById.mockResolvedValue({ id: 'b1', is_active: false });
      await expect(service.create(dto)).rejects.toThrow(BadRequestException);
      expect(mockRepo.save).not.toHaveBeenCalled();
    });
  });

  describe('remove', () => {
    it('soft-deletes an existing account', async () => {
      mockRepo.findByIdWithBank.mockResolvedValue({ id: 'a1' });
      mockRepo.softDelete.mockResolvedValue(undefined);

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

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