package br.gov.sigefp.treasury.service;

import br.gov.sigefp.rh.domain.AgentBankAccount;
import br.gov.sigefp.rh.domain.PayrollItem;
import br.gov.sigefp.rh.domain.PayrollRun;
import br.gov.sigefp.rh.repository.AgentBankAccountRepository;
import br.gov.sigefp.rh.repository.PayrollItemRepository;
import br.gov.sigefp.rh.repository.PayrollRunRepository;
import br.gov.sigefp.treasury.api.dto.CreatePaymentOrderDTO;
import br.gov.sigefp.treasury.api.dto.PaymentOrderDTO;
import br.gov.sigefp.treasury.domain.PaymentBatch;
import br.gov.sigefp.treasury.domain.PaymentOrder;
import br.gov.sigefp.treasury.repository.PaymentBatchRepository;
import br.gov.sigefp.treasury.repository.PaymentOrderRepository;
import br.gov.sigefp.common.exception.BusinessException;
import br.gov.sigefp.common.exception.ResourceNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Serviço de aplicação para gestão de ordens de pagamento.
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class PaymentOrderService implements br.gov.sigefp.common.service.PaymentGenerator {

        private final PaymentOrderRepository paymentOrderRepository;
        private final PaymentBatchRepository paymentBatchRepository;
        private final PayrollRunRepository payrollRunRepository;
        private final PayrollItemRepository payrollItemRepository;
        private final AgentBankAccountRepository agentBankAccountRepository;

        /**
         * Gerar ordens de pagamento a partir de um payrollRun.
         * Cria uma PaymentOrder para cada agente com itens de folha (EARNING).
         * 
         * @param payrollRunId   ID da execução de folha (UUID)
         * @param paymentBatchId ID do lote de pagamento (opcional). Se null, cria novo
         *                       lote.
         */
        @Override
        public void generateOrdersFromPayrollRun(UUID payrollRunId, UUID paymentBatchId) {
                // Implementação adaptada para void (conforme interface)
                generateOrdersInternal(payrollRunId, paymentBatchId);
        }

        /**
         * Método interno que retorna a lista (para uso local ou testes)
         */
        public List<PaymentOrderDTO> generateOrdersInternal(UUID payrollRunId, UUID paymentBatchId) {
                PayrollRun payrollRun = payrollRunRepository.findById(payrollRunId)
                                .orElseThrow(() -> new ResourceNotFoundException(
                                                "Execução de folha não encontrada: " + payrollRunId));

                if (!"COMPLETED".equals(payrollRun.getStatus())) {
                        throw new BusinessException(
                                        "Apenas execuções de folha com status COMPLETED podem gerar ordens de pagamento",
                                        "INVALID_RUN_STATUS", org.springframework.http.HttpStatus.PRECONDITION_FAILED);
                }

                PaymentBatch paymentBatch;
                if (paymentBatchId != null) {
                        paymentBatch = paymentBatchRepository.findById(paymentBatchId)
                                        .orElseThrow(() -> new ResourceNotFoundException(
                                                        "Lote de pagamento não encontrado: " + paymentBatchId));
                } else {
                        // Auto-create PaymentBatch
                        String batchRef = String.format("FOLHA-%d/%d", payrollRun.getPeriod().getMonth(),
                                        payrollRun.getPeriod().getFiscalYear());
                        paymentBatch = PaymentBatch.builder()
                                        .reference(batchRef)
                                        .description("Folha de Pagamento Automática - " + payrollRun.getOrgUnit()) // Simplificado
                                        .status("PENDING")
                                        .createdAt(LocalDateTime.now())
                                        .build();
                        paymentBatch = paymentBatchRepository.save(paymentBatch);
                        log.info("Lote de pagamento criado automaticamente: {}", paymentBatch.getId());
                }

                List<PayrollItem> items = payrollItemRepository.findByPayrollRunId(payrollRunId);

                // Filtrar apenas itens do tipo EARNING (proventos)
                List<PayrollItem> earningItems = items.stream()
                                .filter(item -> "EARNING".equals(item.getLineType()))
                                .collect(Collectors.toList());

                if (earningItems.isEmpty()) {
                        log.warn("Nenhum item de provento encontrado na execução de folha: payrollRunId={}",
                                        payrollRunId);
                        return List.of();
                }

                // Agrupar itens por agente e calcular totais
                Map<UUID, List<PayrollItem>> itemsByAgent = earningItems.stream()
                                .collect(Collectors.groupingBy(PayrollItem::getAgent));

                List<PaymentOrderDTO> createdOrders = new ArrayList<>();

                for (Map.Entry<UUID, List<PayrollItem>> entry : itemsByAgent.entrySet()) {
                        UUID agentId = entry.getKey();
                        List<PayrollItem> agentItems = entry.getValue();

                        // Calcular valores brutos e líquidos
                        BigDecimal grossAmount = agentItems.stream()
                                        .map(item -> item.getTotalAmount() != null ? item.getTotalAmount()
                                                        : BigDecimal.ZERO)
                                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                        // Calcular descontos (itens do tipo DEDUCTION)
                        List<PayrollItem> deductionItems = items.stream()
                                        .filter(item -> agentId.equals(item.getAgent()))
                                        .filter(item -> "DEDUCTION".equals(item.getLineType()))
                                        .collect(Collectors.toList());

                        BigDecimal totalDeductions = deductionItems.stream()
                                        .map(item -> item.getTotalAmount() != null ? item.getTotalAmount()
                                                        : BigDecimal.ZERO)
                                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                        // Calcular Imposto Retido (Deduções com Classificação Econômica Iniciada em "7"
                        // - Receita)
                        BigDecimal taxAmount = deductionItems.stream()
                                        .filter(item -> item.getDeductionType() != null &&
                                                        item.getDeductionType().getEconomicClassCode() != null &&
                                                        item.getDeductionType().getEconomicClassCode().startsWith("7"))
                                        .map(item -> item.getTotalAmount() != null ? item.getTotalAmount()
                                                        : BigDecimal.ZERO)
                                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                        BigDecimal netAmount = grossAmount.subtract(totalDeductions);

                        // Buscar conta bancária principal do agente
                        AgentBankAccount primaryAccount = agentBankAccountRepository
                                        .findByAgentIdAndIsPrimaryTrue(agentId)
                                        .orElse(null);

                        if (primaryAccount == null) {
                                log.warn("Agente sem conta bancária principal: agentId={}, payrollRunId={}", agentId,
                                                payrollRunId);
                                // Continuar sem conta bancária - pode ser preenchida depois
                        }

                        // Usar UUID nativo
                        UUID payrollRunIdVal = payrollRunId;

                        // Buscar budgetLineId do primeiro item do agente (assumindo que todos os itens
                        // do agente
                        // usam a mesma linha orçamentária, ou pegar a primeira encontrada)
                        UUID budgetLineId = agentItems.stream()
                                        .filter(item -> item.getBudgetLine() != null)
                                        .map(PayrollItem::getBudgetLine)
                                        .findFirst()
                                        .orElse(null);

                        // Logica de Integridade Fiscal (Tax Retention)
                        String retentionType = taxAmount.compareTo(BigDecimal.ZERO) > 0 ? "AUTOMATIC_RETENTION"
                                        : "NONE";
                        // TODO: Buscar conta de arrecadação DGI via parâmetro
                        UUID taxCollectionAccount = null;

                        // Criar ordem de pagamento
                        PaymentOrder paymentOrder = PaymentOrder.builder()
                                        .paymentBatch(paymentBatch)
                                        .payrollRunId(payrollRunIdVal)
                                        .agentId(agentId)
                                        .bankAccountId(primaryAccount != null ? primaryAccount.getId() : null)
                                        .budgetLineId(budgetLineId)
                                        .grossAmount(grossAmount)
                                        .netAmount(netAmount)
                                        .taxAmount(taxAmount)
                                        .taxRetentionType(retentionType)
                                        .taxCollectionAccountId(taxCollectionAccount)
                                        .status("CREATED")
                                        .build();

                        PaymentOrder saved = paymentOrderRepository.save(paymentOrder);
                        createdOrders.add(toDTO(saved));

                        log.info("Ordem criada com Integridade Fiscal: orderId={}, net={}, tax={}",
                                        saved.getId(), netAmount, taxAmount);
                }

                log.info("Total de ordens de pagamento criadas: {} para payrollRunId={}",
                                createdOrders.size(), payrollRunId);

                // Atualizar totais do lote
                // (Opcional, mas boa prática)

                return createdOrders;
        }

        public PaymentOrderDTO create(CreatePaymentOrderDTO dto) {
                PaymentBatch paymentBatch = paymentBatchRepository.findById(dto.getPaymentBatchId())
                                .orElseThrow(() -> new ResourceNotFoundException(
                                                "Lote de pagamento não encontrado: " + dto.getPaymentBatchId()));

                PaymentOrder paymentOrder = PaymentOrder.builder()
                                .paymentBatch(paymentBatch)
                                .payrollRunId(dto.getPayrollRunId())
                                .agentId(dto.getAgentId())
                                .bankAccountId(dto.getBankAccountId())
                                .grossAmount(dto.getGrossAmount())
                                .netAmount(dto.getNetAmount())
                                .taxAmount(dto.getTaxAmount() != null ? dto.getTaxAmount() : BigDecimal.ZERO)
                                .taxRetentionType(dto.getTaxRetentionType())
                                .taxCollectionAccountId(dto.getTaxCollectionAccountId())
                                .status("CREATED")
                                .build();

                PaymentOrder saved = paymentOrderRepository.save(paymentOrder);
                return toDTO(saved);
        }

        public PaymentOrderDTO updateStatus(UUID id, String status) {
                PaymentOrder paymentOrder = paymentOrderRepository.findById(id)
                                .orElseThrow(() -> new IllegalArgumentException(
                                                "Ordem de pagamento não encontrada: " + id));

                paymentOrder.setStatus(status);
                PaymentOrder saved = paymentOrderRepository.save(paymentOrder);
                return toDTO(saved);
        }

        @Transactional(readOnly = true)
        public PaymentOrderDTO findById(UUID id) {
                PaymentOrder paymentOrder = paymentOrderRepository.findById(id)
                                .orElseThrow(() -> new IllegalArgumentException(
                                                "Ordem de pagamento não encontrada: " + id));
                return toDTO(paymentOrder);
        }

        @Transactional(readOnly = true)
        public Page<PaymentOrderDTO> findAll(Pageable pageable) {
                return paymentOrderRepository.findAll(pageable).map(this::toDTO);
        }

        @Transactional(readOnly = true)
        public List<PaymentOrderDTO> findByPaymentBatchId(UUID batchId) {
                return paymentOrderRepository.findByPaymentBatchId(batchId).stream()
                                .map(this::toDTO)
                                .collect(Collectors.toList());
        }

        @Transactional(readOnly = true)
        public List<PaymentOrderDTO> findByStatus(String status) {
                return paymentOrderRepository.findByStatus(status).stream()
                                .map(this::toDTO)
                                .collect(Collectors.toList());
        }

        @Transactional(readOnly = true)
        public List<PaymentOrderDTO> findByPaymentBatchIdAndStatus(UUID batchId, String status) {
                return paymentOrderRepository.findByPaymentBatchIdAndStatus(batchId, status).stream()
                                .map(this::toDTO)
                                .collect(Collectors.toList());
        }

        private PaymentOrderDTO toDTO(PaymentOrder paymentOrder) {
                return PaymentOrderDTO.builder()
                                .id(paymentOrder.getId())
                                .paymentBatchId(paymentOrder.getPaymentBatch().getId())
                                .payrollRunId(paymentOrder.getPayrollRunId())
                                .agentId(paymentOrder.getAgentId())
                                .bankAccountId(paymentOrder.getBankAccountId())
                                .budgetLineId(paymentOrder.getBudgetLineId())
                                .grossAmount(paymentOrder.getGrossAmount())
                                .netAmount(paymentOrder.getNetAmount())
                                .taxAmount(paymentOrder.getTaxAmount())
                                .taxRetentionType(paymentOrder.getTaxRetentionType())
                                .taxCollectionAccountId(paymentOrder.getTaxCollectionAccountId())
                                .status(paymentOrder.getStatus())
                                .build();
        }
}
