package br.gov.sigefp.budget.service;

import br.gov.sigefp.budget.api.dto.BudgetEntryDTO;
import br.gov.sigefp.budget.api.dto.CreateBudgetEntryDTO;
import br.gov.sigefp.budget.domain.BudgetAllocation;
import br.gov.sigefp.budget.domain.BudgetEntry;
import br.gov.sigefp.budget.domain.BudgetLine;
import br.gov.sigefp.budget.domain.BudgetEntryType;
import br.gov.sigefp.budget.repository.BudgetAllocationRepository;
import br.gov.sigefp.budget.repository.BudgetEntryRepository;
import br.gov.sigefp.budget.repository.BudgetLineRepository;
import br.gov.sigefp.budget.repository.BudgetExecutionRepository;
import br.gov.sigefp.common.exception.BusinessException;
import br.gov.sigefp.common.exception.ResourceNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
@Transactional
public class BudgetEntryService {

    private final BudgetEntryRepository budgetEntryRepository;
    private final BudgetAllocationRepository budgetAllocationRepository;
    private final BudgetLineRepository budgetLineRepository;
    private final BudgetExecutionRepository budgetExecutionRepository;

    public BudgetEntryDTO create(CreateBudgetEntryDTO dto) {
        BudgetLine budgetLine = budgetLineRepository.findById(dto.getBudgetLineId())
                .orElseThrow(() -> new ResourceNotFoundException(
                        "Linha orçamentária não encontrada: " + dto.getBudgetLineId()));

        // Validação: Data da transação deve estar dentro do exercício fiscal
        if (dto.getTransactionDate().isBefore(budgetLine.getFiscalYear().getStartDate()) ||
                dto.getTransactionDate().isAfter(budgetLine.getFiscalYear().getEndDate())) {
            throw new BusinessException(
                    String.format("Data da transação (%s) deve estar dentro do exercício fiscal (%s a %s)",
                            dto.getTransactionDate(),
                            budgetLine.getFiscalYear().getStartDate(),
                            budgetLine.getFiscalYear().getEndDate()),
                    "INVALID_TRANSACTION_DATE", HttpStatus.BAD_REQUEST);
        }

        // Validação: TRANSFER_OUT e CANCELLATION não podem exceder saldo disponível
        if (dto.getType() == BudgetEntryType.TRANSFER_OUT || dto.getType() == BudgetEntryType.CANCELLATION) {
            BigDecimal totalAllocated = budgetAllocationRepository
                    .calculateTotalAllocatedByBudgetLineId(budgetLine.getId());
            BigDecimal totalCommitted = budgetExecutionRepository
                    .calculateTotalCommittedByBudgetLineId(budgetLine.getId());
            BigDecimal available = totalAllocated.subtract(totalCommitted);

            if (dto.getAmount().compareTo(available) > 0) {
                throw new BusinessException(
                        String.format("Transferência/Cancelamento não pode exceder saldo disponível. Disponível: %s, Solicitado: %s",
                                available, dto.getAmount()),
                        "INSUFFICIENT_BALANCE", HttpStatus.CONFLICT);
            }
        }

        // 1. Create the Audit Entry
        BudgetEntry entry = BudgetEntry.builder()
                .budgetLine(budgetLine)
                .type(dto.getType())
                .amount(dto.getAmount())
                .transactionDate(dto.getTransactionDate())
                .documentReference(dto.getDocumentReference())
                .description(dto.getDescription())
                .build();

        BudgetEntry savedEntry = budgetEntryRepository.save(entry);

        // 2. Reflect impact in BudgetAllocation
        // We create a NEW allocation record for each entry to keep the sum correct
        // according to the repository's logic: SUM(initialAmount + adjustmentAmount)
        BigDecimal initialAmount = BigDecimal.ZERO;
        BigDecimal adjustmentAmount = BigDecimal.ZERO;

        if (dto.getType() == BudgetEntryType.INITIAL_ALLOCATION) {
            initialAmount = dto.getAmount();
        } else {
            // For other types, it's an adjustment
            // Check if it adds or subtracts
            if (dto.getType() == BudgetEntryType.CANCELLATION || dto.getType() == BudgetEntryType.TRANSFER_OUT) {
                adjustmentAmount = dto.getAmount().negate();
            } else {
                adjustmentAmount = dto.getAmount();
            }
        }

        BudgetAllocation allocation = BudgetAllocation.builder()
                .budgetLine(budgetLine)
                .initialAmount(initialAmount)
                .adjustmentAmount(adjustmentAmount)
                .build();

        budgetAllocationRepository.save(allocation);

        return toDTO(savedEntry);
    }

    @Transactional(readOnly = true)
    public Page<BudgetEntryDTO> findByBudgetLineId(UUID budgetLineId, Pageable pageable) {
        return budgetEntryRepository.findByBudgetLineId(budgetLineId, pageable)
                .map(this::toDTO);
    }

    private BudgetEntryDTO toDTO(BudgetEntry entry) {
        return BudgetEntryDTO.builder()
                .id(entry.getId())
                .budgetLineId(entry.getBudgetLine().getId())
                .type(entry.getType())
                .amount(entry.getAmount())
                .transactionDate(entry.getTransactionDate())
                .documentReference(entry.getDocumentReference())
                .description(entry.getDescription())
                .build();
    }
}
