package br.gov.sigefp.treasury.service;

import br.gov.sigefp.common.exception.BusinessException;
import br.gov.sigefp.treasury.api.dto.CreatePaymentAuthorizationDTO;
import br.gov.sigefp.treasury.api.dto.PaymentAuthorizationDTO;
import br.gov.sigefp.treasury.domain.Approval;
import br.gov.sigefp.treasury.domain.PaymentAuthorization;
import br.gov.sigefp.treasury.repository.ApprovalRepository;
import br.gov.sigefp.treasury.repository.PaymentAuthorizationRepository;
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.time.LocalDateTime;
import java.util.ArrayList;
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 PaymentAuthorizationServiceTest {

    @Mock
    private PaymentAuthorizationRepository authorizationRepository;

    @Mock
    private ApprovalRepository approvalRepository;

    @InjectMocks
    private PaymentAuthorizationService authorizationService;

    private PaymentAuthorization authorization;
    private CreatePaymentAuthorizationDTO createDTO;

    @BeforeEach
    void setUp() {
        authorization = PaymentAuthorization.builder()
                .id(UUID.randomUUID())
                .paymentOrderId(UUID.randomUUID())
                .requestedBy(UUID.randomUUID())
                .requestedAt(LocalDateTime.now())
                .requiredApprovalLevel(2)
                .currentApprovalLevel(1)
                .status("PENDING")
                .approvals(new ArrayList<>())
                .build();

        createDTO = new CreatePaymentAuthorizationDTO();
        createDTO.setPaymentOrderId(authorization.getPaymentOrderId());
        createDTO.setRequestedBy(authorization.getRequestedBy());
        createDTO.setRequiredApprovalLevel(2);
    }

    @Test
    @DisplayName("Should request authorization successfully")
    void requestAuthorization_Success() {
        when(authorizationRepository.save(any(PaymentAuthorization.class))).thenReturn(authorization);

        PaymentAuthorizationDTO result = authorizationService.requestAuthorization(createDTO);

        assertNotNull(result);
        assertEquals("PENDING", result.getStatus());
        assertEquals(1, result.getCurrentApprovalLevel());
        verify(authorizationRepository).save(any(PaymentAuthorization.class));
    }

    @Test
    @DisplayName("Should approve authorization successfully (Partial Approval)")
    void approve_PartialSuccess() {
        // Setup: Level 1 of 2
        when(authorizationRepository.findById(authorization.getId())).thenReturn(Optional.of(authorization));
        when(authorizationRepository.save(any(PaymentAuthorization.class))).thenReturn(authorization);
        // Approval repository save is called, mock it? Not strictly needed for
        // void/save return unless captured

        PaymentAuthorizationDTO result = authorizationService.approve(authorization.getId(), UUID.randomUUID(),
                "Looks good");

        // Verify status changed to PARTIALLY_APPROVED and level incremented
        // Note: The service modifies the object in place, so our mock return reflects
        // that if we return the SAME object
        assertEquals("PARTIALLY_APPROVED", result.getStatus());
        assertEquals(2, result.getCurrentApprovalLevel());
        verify(approvalRepository).save(any(Approval.class));
    }

    @Test
    @DisplayName("Should finalize approval when all levels are met")
    void approve_FinalSuccess() {
        // Setup: Level 2 of 2 (Already at level 2 manually for this test case?)
        // Or simpler: Current Level is 1, Required is 1.
        authorization.setRequiredApprovalLevel(1);

        when(authorizationRepository.findById(authorization.getId())).thenReturn(Optional.of(authorization));
        when(authorizationRepository.save(any(PaymentAuthorization.class))).thenReturn(authorization);

        PaymentAuthorizationDTO result = authorizationService.approve(authorization.getId(), UUID.randomUUID(),
                "Final OK");

        assertEquals("APPROVED", result.getStatus());
        verify(approvalRepository).save(any(Approval.class));
    }

    @Test
    @DisplayName("Should reject authorization successfully")
    void reject_Success() {
        when(authorizationRepository.findById(authorization.getId())).thenReturn(Optional.of(authorization));
        when(authorizationRepository.save(any(PaymentAuthorization.class))).thenReturn(authorization);

        PaymentAuthorizationDTO result = authorizationService.reject(authorization.getId(), UUID.randomUUID(),
                "Bad Payment");

        assertEquals("REJECTED", result.getStatus());
        assertEquals("Bad Payment", result.getRejectionReason());
    }

    @Test
    @DisplayName("Should calculate required level correctly")
    void calculateRequiredLevel_Logic() {
        // <= 100,000 -> 1
        assertEquals(1, authorizationService.calculateRequiredLevel(new BigDecimal("50000")));
        assertEquals(1, authorizationService.calculateRequiredLevel(new BigDecimal("100000")));

        // 100,001 - 500,000 -> 2
        assertEquals(2, authorizationService.calculateRequiredLevel(new BigDecimal("100001")));
        assertEquals(2, authorizationService.calculateRequiredLevel(new BigDecimal("500000")));

        // > 500,000 -> 3
        assertEquals(3, authorizationService.calculateRequiredLevel(new BigDecimal("500001")));
    }
}
