package br.gov.sigefp.rh.service;

import br.gov.sigefp.budget.integration.BudgetIntegrationService;
import br.gov.sigefp.rh.domain.*;
import br.gov.sigefp.rh.repository.*; // Assume this covers FamilyAllowanceTableRepository if it's in the same package
import br.gov.sigefp.common.exception.BusinessException;
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.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class PayrollServiceTest {

        @Mock
        private PayrollPeriodRepository payrollPeriodRepository;
        @Mock
        private PayrollRunRepository payrollRunRepository;
        @Mock
        private PayrollItemRepository payrollItemRepository;
        @Mock
        private AgentRepository agentRepository;
        @Mock
        private SalaryGridRepository salaryGridRepository;
        @Mock
        private EarningTypeRepository earningTypeRepository;
        @Mock
        private GlobalDeductionRuleRepository globalDeductionRuleRepository;
        @Mock
        private TaxBracketRepository taxBracketRepository;
        @Mock
        private AttendanceRecordRepository attendanceRecordRepository;
        @Mock
        private AbsenceRepository absenceRepository;
        @Mock
        private br.gov.sigefp.budget.repository.BudgetLineRepository budgetLineRepository;
        @Mock
        private br.gov.sigefp.budget.repository.FiscalYearRepository fiscalYearRepository;
        @Mock
        private BudgetIntegrationService budgetIntegrationService;

        @Mock
        private AgentContractRepository agentContractRepository;
        @Mock
        private FamilyAllowanceTableRepository familyAllowanceTableRepository;
        @Mock
        private br.gov.sigefp.common.service.PaymentGenerator paymentGenerator;
        @Mock
        private DeductionTypeRepository deductionTypeRepository;

        @InjectMocks
        private PayrollService payrollService;

        private UUID payrollRunId;
        private PayrollRun payrollRun;
        private Agent agent;
        private EarningType baseSalaryType;
        private EarningType familyAllowanceType;
        private EarningType subsidyType;
        private AgentContract contract;

        @BeforeEach
        void setUp() {
                payrollRunId = UUID.randomUUID();
                PayrollPeriod period = PayrollPeriod.builder()
                                .fiscalYear(2024)
                                .month(1)
                                .startDate(LocalDate.of(2024, 1, 1))
                                .endDate(LocalDate.of(2024, 1, 31))
                                .build();

                payrollRun = PayrollRun.builder()
                                .id(payrollRunId)
                                .period(period)
                                .status("PENDING")
                                .build();

                agent = Agent.builder()
                                .id(UUID.randomUUID())
                                .fullName("Test Agent")
                                .status("ACTIVE")
                                .salaryStep(UUID.randomUUID())
                                .posseDate(LocalDate.of(2020, 1, 1))
                                .eligibleDependentsCount(2)
                                .build();

                contract = AgentContract.builder()
                                .agent(agent)
                                .isActive(true)
                                .startDate(LocalDate.of(2020, 1, 1))
                                .build();

                baseSalaryType = EarningType.builder()
                                .id(UUID.randomUUID())
                                .code("SALARIO_BASE")
                                .name("Vencimento Base")
                                .build();

                familyAllowanceType = EarningType.builder()
                                .id(UUID.randomUUID())
                                .code("ABONO_FAMILIA")
                                .name("Abono de Família")
                                .build();

                subsidyType = EarningType.builder()
                                .id(UUID.randomUUID())
                                .code("SUBSIDIO")
                                .name("Subsídio")
                                .build();
        }

        @Test
        @DisplayName("Deve gerar itens de folha com cálculos de INPS e IRPS corretos")
        void generatePayrollItems_WithSuccess() {
                // Mocking
                when(payrollRunRepository.findById(payrollRunId)).thenReturn(Optional.of(payrollRun));
                when(earningTypeRepository.findByCode("SALARIO_BASE")).thenReturn(Optional.of(baseSalaryType));
                when(earningTypeRepository.findByCode("ABONO_FAMILIA")).thenReturn(Optional.of(familyAllowanceType));
                when(earningTypeRepository.findByCode("SUBSIDIO")).thenReturn(Optional.of(subsidyType));
                when(agentRepository.findByStatus("ACTIVE")).thenReturn(List.of(agent));

                // MOCK BATCH FETCHES
                when(agentContractRepository.findByAgentIdIn(anyList())).thenReturn(List.of(contract));
                when(attendanceRecordRepository.findByAgentIdInAndDateRange(any(), any(), any())).thenReturn(List.of());
                when(absenceRepository.findByAgentIdInAndDateRange(any(), any(), any())).thenReturn(List.of());
                // when(budgetLineRepository.findByFiscalYearId(any())).thenReturn(List.of());
                // // Not strictly needed if returns empty by default mock? better explicit.

                // Salário Base: 100.000
                BigDecimal baseAmount = new BigDecimal("100000");
                SalaryStep step = SalaryStep.builder().build();
                step.setId(agent.getSalaryStep()); // Ensure ID matches agent

                SalaryGrid salaryGrid = SalaryGrid.builder()
                                .baseAmount(baseAmount)
                                .step(step)
                                .validFrom(LocalDate.of(2020, 1, 1))
                                .build();

                // MOCK findAll for Batch Fetch
                when(salaryGridRepository.findAll()).thenReturn(List.of(salaryGrid));
                // when(salaryGridRepository.findByStepIdAndDate(any(),
                // any())).thenReturn(salaryGrid); // Removing old mock

                // Regras Globais: INPS (7%) e Selo (0.3%)
                DeductionType inpsType = DeductionType.builder().code("INPS").name("INPS").build();
                GlobalDeductionRule inpsRule = GlobalDeductionRule.builder()
                                .deductionType(inpsType)
                                .percentage(new BigDecimal("0.07"))
                                .build();

                // MOCK FISCAL YEAR & BUDGET LINE
                br.gov.sigefp.budget.domain.FiscalYear fy = br.gov.sigefp.budget.domain.FiscalYear.builder()
                                .id(UUID.randomUUID())
                                .year(2024)
                                .build();
                when(fiscalYearRepository.findByYear(2024)).thenReturn(Optional.of(fy));
                when(budgetLineRepository.findByFiscalYearId(any())).thenReturn(List.of());

                DeductionType seloType = DeductionType.builder().code("SELO").name("Imposto de Selo").build();
                GlobalDeductionRule seloRule = GlobalDeductionRule.builder()
                                .deductionType(seloType)
                                .percentage(new BigDecimal("0.003"))
                                .build();

                when(globalDeductionRuleRepository.findActiveRules(any())).thenReturn(List.of(inpsRule, seloRule));

                // IRPS: Faixa de 10% para base > 50.000 (Exemplo simples)
                DeductionType irpsType = DeductionType.builder().code("IRPS").name("IRPS").build();
                TaxBracket irpsBracket = TaxBracket.builder()
                                .deductionType(irpsType)
                                .lowerLimit(new BigDecimal("50001"))
                                .upperLimit(new BigDecimal("150000"))
                                .ratePercentage(new BigDecimal("0.10"))
                                .excessDeduction(new BigDecimal("5000"))
                                .build();

                when(taxBracketRepository.findActiveBrackets(any())).thenReturn(List.of(irpsBracket));

                // Execute
                payrollService.generatePayrollItems(payrollRunId);

                // Verify Items Saved
                // 1. Salário Base (100.000)
                // 2. Abono Família (2000)
                // Bruto = 102.000
                // 3. INPS (102.000 * 0.07 = 7.140)
                // 4. Selo (102.000 * 0.003 = 306)
                // Base IRPS = 102.000 - 7.140 = 94.860
                // 5. IRPS (94.860 * 0.10 - 5.000 = 9.486 - 5.000 = 4.486)

                verify(payrollItemRepository, atLeast(5)).save(any(PayrollItem.class));

                // Vamos verificar os valores específicos se possível via capture (opcional aqui
                // para brevidade)
                verify(payrollRunRepository).save(argThat(run -> "GENERATED".equals(run.getStatus())));
        }

        @Test
        @DisplayName("Deve falhar ao processar folha sem itens orçamentados")
        void processPayrollRun_FailsWhenBudgetLineMissing() {
                payrollRun.setStatus("GENERATED");
                when(payrollRunRepository.findById(payrollRunId)).thenReturn(Optional.of(payrollRun));

                PayrollItem itemWithoutBudget = PayrollItem.builder()
                                .id(UUID.randomUUID())
                                .totalAmount(new BigDecimal("1000"))
                                .budgetLine(null) // Erro proposital
                                .build();

                when(payrollItemRepository.findByPayrollRunId(payrollRunId)).thenReturn(List.of(itemWithoutBudget));

                assertThrows(BusinessException.class, () -> payrollService.processPayrollRun(payrollRunId));
        }
}
