package br.gov.sigefp.treasury.service;

import br.gov.sigefp.common.exception.BusinessException;
import br.gov.sigefp.common.exception.ResourceNotFoundException;
import br.gov.sigefp.treasury.api.dto.CashAccountDTO;
import br.gov.sigefp.treasury.api.dto.CreateCashAccountDTO;
import br.gov.sigefp.treasury.domain.CashAccount;
import br.gov.sigefp.treasury.repository.CashAccountRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class CashAccountServiceTest {

    @Mock
    private CashAccountRepository cashAccountRepository;

    @InjectMocks
    private CashAccountService cashAccountService;

    private CashAccount cashAccount;
    private CreateCashAccountDTO createDTO;

    @BeforeEach
    void setUp() {
        cashAccount = CashAccount.builder()
                .id(UUID.randomUUID())
                .code("CAIXA-001")
                .name("Caixa Principal")
                .type("CASH")
                .currentBalance(new BigDecimal("1000.00"))
                .availableBalance(new BigDecimal("1000.00"))
                .isActive(true)
                .build();

        createDTO = new CreateCashAccountDTO();
        createDTO.setCode("CAIXA-001");
        createDTO.setName("Caixa Principal");
        createDTO.setType("CASH");
    }

    @Test
    @DisplayName("Should create cash account successfully")
    void create_Success() {
        when(cashAccountRepository.findByCode(createDTO.getCode())).thenReturn(Optional.empty());
        when(cashAccountRepository.save(any(CashAccount.class))).thenReturn(cashAccount);

        CashAccountDTO result = cashAccountService.create(createDTO);

        assertNotNull(result);
        assertEquals(cashAccount.getCode(), result.getCode());
        verify(cashAccountRepository).save(any(CashAccount.class));
    }

    @Test
    @DisplayName("Should throw exception when creating duplicate code")
    void create_DuplicateCode() {
        when(cashAccountRepository.findByCode(createDTO.getCode())).thenReturn(Optional.of(cashAccount));

        assertThrows(BusinessException.class, () -> cashAccountService.create(createDTO));
        verify(cashAccountRepository, never()).save(any(CashAccount.class));
    }

    @Test
    @DisplayName("Should update balance correctly on INFLOW")
    void updateBalance_Inflow() {
        when(cashAccountRepository.findById(cashAccount.getId())).thenReturn(Optional.of(cashAccount));
        when(cashAccountRepository.save(any(CashAccount.class))).thenReturn(cashAccount);

        BigDecimal amount = new BigDecimal("500.00");
        cashAccountService.updateBalance(cashAccount.getId(), amount, "INFLOW");

        assertEquals(new BigDecimal("1500.00"), cashAccount.getCurrentBalance());
        assertEquals(new BigDecimal("1500.00"), cashAccount.getAvailableBalance());
    }

    @Test
    @DisplayName("Should update balance correctly on OUTFLOW")
    void updateBalance_Outflow() {
        when(cashAccountRepository.findById(cashAccount.getId())).thenReturn(Optional.of(cashAccount));
        when(cashAccountRepository.save(any(CashAccount.class))).thenReturn(cashAccount);

        BigDecimal amount = new BigDecimal("200.00");
        cashAccountService.updateBalance(cashAccount.getId(), amount, "OUTFLOW");

        assertEquals(new BigDecimal("800.00"), cashAccount.getCurrentBalance());
        assertEquals(new BigDecimal("800.00"), cashAccount.getAvailableBalance());
    }

    @Test
    @DisplayName("Should commit balance successfully")
    void commitBalance_Success() {
        when(cashAccountRepository.findById(cashAccount.getId())).thenReturn(Optional.of(cashAccount));
        when(cashAccountRepository.save(any(CashAccount.class))).thenReturn(cashAccount);

        BigDecimal amount = new BigDecimal("300.00");
        cashAccountService.commitBalance(cashAccount.getId(), amount);

        assertEquals(new BigDecimal("1000.00"), cashAccount.getCurrentBalance()); // Current unchangd
        assertEquals(new BigDecimal("700.00"), cashAccount.getAvailableBalance()); // Available reduced
    }

    @Test
    @DisplayName("Should throw exception when committing more than available")
    void commitBalance_InsufficientFunds() {
        when(cashAccountRepository.findById(cashAccount.getId())).thenReturn(Optional.of(cashAccount));

        BigDecimal amount = new BigDecimal("1500.00"); // More than 1000

        assertThrows(BusinessException.class, () -> cashAccountService.commitBalance(cashAccount.getId(), amount));

        assertEquals(new BigDecimal("1000.00"), cashAccount.getAvailableBalance()); // Should not have changed
    }

    @Test
    @DisplayName("Should release balance correctly")
    void releaseBalance_Success() {
        when(cashAccountRepository.findById(cashAccount.getId())).thenReturn(Optional.of(cashAccount));
        when(cashAccountRepository.save(any(CashAccount.class))).thenReturn(cashAccount);

        // Assume some blocked balance, available is 1000
        BigDecimal amount = new BigDecimal("200.00");
        cashAccountService.releaseBalance(cashAccount.getId(), amount);

        assertEquals(new BigDecimal("1200.00"), cashAccount.getAvailableBalance());
    }
}
