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.CreateTreasuryPlanDTO;
import br.gov.sigefp.treasury.api.dto.TreasuryPlanDTO;
import br.gov.sigefp.treasury.domain.TreasuryPlan;
import br.gov.sigefp.treasury.repository.TreasuryPlanRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Serviço de aplicação para gestão de Planos de Tesouraria (PT).
 * Conforme Master Plan - Module 2: Preventive Control.
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class TreasuryPlanService {

    private final TreasuryPlanRepository treasuryPlanRepository;

    /**
     * Cria um novo plano de tesouraria.
     */
    public TreasuryPlanDTO createPlan(CreateTreasuryPlanDTO dto) {
        // Verificar se já existe plano para o mesmo ano/mês
        treasuryPlanRepository.findByFiscalYearAndReferenceMonth(
                dto.getFiscalYear(), dto.getReferenceMonth())
                .ifPresent(existing -> {
                    throw new BusinessException(
                            String.format("Já existe um plano para o ano %d, mês %d", 
                                    dto.getFiscalYear(), dto.getReferenceMonth()),
                            "DUPLICATE_PLAN",
                            org.springframework.http.HttpStatus.CONFLICT);
                });

        // Calcular datas se não fornecidas
        LocalDate startDate = dto.getStartDate();
        LocalDate endDate = dto.getEndDate();
        
        if (startDate == null || endDate == null) {
            YearMonth yearMonth = YearMonth.of(dto.getFiscalYear(), dto.getReferenceMonth());
            startDate = yearMonth.atDay(1);
            endDate = yearMonth.atEndOfMonth();
        }

        // Validar datas
        if (endDate.isBefore(startDate)) {
            throw new BusinessException(
                    "Data de fim deve ser posterior à data de início",
                    "INVALID_DATE_RANGE",
                    org.springframework.http.HttpStatus.BAD_REQUEST);
        }

        TreasuryPlan plan = TreasuryPlan.builder()
                .fiscalYear(dto.getFiscalYear())
                .referenceMonth(dto.getReferenceMonth())
                .approvedCeiling(dto.getApprovedCeiling())
                .executedAmount(BigDecimal.ZERO)
                .startDate(startDate)
                .endDate(endDate)
                .status("DRAFT")
                .build();

        TreasuryPlan saved = treasuryPlanRepository.save(plan);
        log.info("Plano de Tesouraria criado: id={}, fiscalYear={}, month={}, ceiling={}", 
                saved.getId(), saved.getFiscalYear(), saved.getReferenceMonth(), saved.getApprovedCeiling());

        return toDTO(saved);
    }

    /**
     * Aprova um plano de tesouraria.
     */
    public TreasuryPlanDTO approvePlan(UUID planId, UUID approverId) {
        TreasuryPlan plan = treasuryPlanRepository.findById(planId)
                .orElseThrow(() -> new ResourceNotFoundException("Plano não encontrado: " + planId));

        if (!"DRAFT".equals(plan.getStatus())) {
            throw new BusinessException(
                    "Apenas planos em rascunho podem ser aprovados",
                    "INVALID_STATUS",
                    org.springframework.http.HttpStatus.CONFLICT);
        }

        // Fechar planos anteriores sobrepostos
        List<TreasuryPlan> overlappingPlans = treasuryPlanRepository.findApprovedPlansForPeriod(
                plan.getStartDate(), plan.getEndDate());
        
        for (TreasuryPlan overlapping : overlappingPlans) {
            if (overlapping.getStatus().equals("APPROVED")) {
                overlapping.setStatus("CLOSED");
                treasuryPlanRepository.save(overlapping);
                log.info("Plano sobreposto fechado: id={}", overlapping.getId());
            }
        }

        plan.setStatus("APPROVED");
        plan.setApprovedBy(approverId);
        plan.setApprovedAt(java.time.LocalDateTime.now());

        TreasuryPlan saved = treasuryPlanRepository.save(plan);
        log.info("Plano de Tesouraria aprovado: id={}, approvedBy={}", planId, approverId);

        return toDTO(saved);
    }

    /**
     * Valida disponibilidade de fundos conforme o plano ativo.
     * Conforme Master Plan 2.3: Integration Point.
     * 
     * @param amount Valor a ser validado
     * @return true se há disponibilidade, false caso contrário
     * @throws BusinessException se o teto for excedido
     */
    public boolean validateAvailability(BigDecimal amount) {
        LocalDate today = LocalDate.now();
        
        TreasuryPlan activePlan = treasuryPlanRepository.findActivePlanForDate(today)
                .orElse(null);

        if (activePlan == null) {
            log.warn("Nenhum plano ativo encontrado para a data: {}", today);
            // Se não houver plano, permitir (pode ser configurável)
            return true;
        }

        BigDecimal available = activePlan.getAvailableAmount();
        
        if (amount.compareTo(available) > 0) {
            throw new BusinessException(
                    String.format("Valor excede o teto disponível no Plano de Tesouraria. " +
                            "Disponível: %s, Solicitado: %s, Plano: %d/%d", 
                            available, amount, activePlan.getFiscalYear(), activePlan.getReferenceMonth()),
                    "CEILING_EXCEEDED",
                    org.springframework.http.HttpStatus.CONFLICT);
        }

        return true;
    }

    /**
     * Atualiza o valor executado do plano (chamado após autorização de pagamento).
     */
    public void updateExecutedAmount(UUID planId, BigDecimal amount) {
        TreasuryPlan plan = treasuryPlanRepository.findById(planId)
                .orElseThrow(() -> new ResourceNotFoundException("Plano não encontrado: " + planId));

        plan.setExecutedAmount(plan.getExecutedAmount().add(amount));
        treasuryPlanRepository.save(plan);
        
        log.debug("Valor executado atualizado: planId={}, newExecutedAmount={}", 
                planId, plan.getExecutedAmount());
    }

    /**
     * Encontra o plano ativo para uma data específica.
     */
    @Transactional(readOnly = true)
    public TreasuryPlanDTO findActivePlanForDate(LocalDate date) {
        return treasuryPlanRepository.findActivePlanForDate(date)
                .map(this::toDTO)
                .orElse(null);
    }

    @Transactional(readOnly = true)
    public TreasuryPlanDTO findById(UUID id) {
        TreasuryPlan plan = treasuryPlanRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Plano não encontrado: " + id));
        return toDTO(plan);
    }

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

    /**
     * Calcula o valor executado para um plano específico.
     */
    @Transactional(readOnly = true)
    public BigDecimal calculateExecutedAmount(UUID planId) {
        TreasuryPlan plan = treasuryPlanRepository.findById(planId)
                .orElseThrow(() -> new ResourceNotFoundException("Plano não encontrado: " + planId));
        
        return treasuryPlanRepository.calculateExecutedAmountForPeriod(
                plan.getStartDate(), plan.getEndDate());
    }

    private TreasuryPlanDTO toDTO(TreasuryPlan plan) {
        return TreasuryPlanDTO.builder()
                .id(plan.getId())
                .fiscalYear(plan.getFiscalYear())
                .referenceMonth(plan.getReferenceMonth())
                .status(plan.getStatus())
                .approvedCeiling(plan.getApprovedCeiling())
                .executedAmount(plan.getExecutedAmount())
                .availableAmount(plan.getAvailableAmount())
                .startDate(plan.getStartDate())
                .endDate(plan.getEndDate())
                .approvedBy(plan.getApprovedBy())
                .approvedAt(plan.getApprovedAt())
                .createdAt(plan.getCreatedAt())
                .updatedAt(plan.getUpdatedAt())
                .build();
    }
}

