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.CreateTreasuryEntryDTO;
import br.gov.sigefp.treasury.api.dto.TreasuryEntryDTO;
import br.gov.sigefp.treasury.domain.CashAccount;
import br.gov.sigefp.treasury.domain.TreasuryEntry;
import br.gov.sigefp.treasury.domain.TreasuryEntryType;
import br.gov.sigefp.treasury.repository.CashAccountRepository;
import br.gov.sigefp.treasury.repository.TreasuryEntryRepository;
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.UUID;

/**
 * Serviço de aplicação para gestão de entradas de tesouraria.
 * Similar a BudgetEntryService no módulo de Orçamento.
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class TreasuryEntryService {

    private final TreasuryEntryRepository treasuryEntryRepository;
    private final CashAccountRepository cashAccountRepository;
    private final CashAccountService cashAccountService;

    public TreasuryEntryDTO create(CreateTreasuryEntryDTO dto) {
        // Validar conta de caixa
        CashAccount cashAccount = cashAccountRepository.findById(dto.getCashAccountId())
                .orElseThrow(() -> new ResourceNotFoundException(
                    "Conta de caixa não encontrada: " + dto.getCashAccountId()));

        // Validar disponibilidade de caixa para saídas
        if (isOutflowType(dto.getType()) && dto.getAmount() != null) {
            BigDecimal available = cashAccountService.getAvailableBalance(dto.getCashAccountId());
            if (dto.getAmount().compareTo(available) > 0) {
                throw new BusinessException(
                    String.format("Saldo disponível insuficiente. Disponível: %s, Solicitado: %s",
                        available, dto.getAmount()),
                    "INSUFFICIENT_CASH",
                    org.springframework.http.HttpStatus.CONFLICT);
            }
        }

        TreasuryEntry entry = TreasuryEntry.builder()
                .cashAccount(cashAccount)
                .type(dto.getType())
                .amount(dto.getAmount())
                .transactionDate(dto.getTransactionDate())
                .documentReference(dto.getDocumentReference())
                .description(dto.getDescription())
                .status(dto.getStatus() != null ? dto.getStatus() : "DRAFT")
                .approvalLevel(dto.getApprovalLevel())
                .paymentOrderId(dto.getPaymentOrderId())
                .paymentBatchId(dto.getPaymentBatchId())
                .budgetLineId(dto.getBudgetLineId())
                .build();

        TreasuryEntry saved = treasuryEntryRepository.save(entry);

        // Atualizar saldo da conta se necessário
        updateCashAccountBalance(saved);

        log.info("Entrada de tesouraria criada: id={}, type={}, amount={}", 
                saved.getId(), saved.getType(), saved.getAmount());

        return toDTO(saved);
    }

    @Transactional(readOnly = true)
    public TreasuryEntryDTO findById(UUID id) {
        TreasuryEntry entry = treasuryEntryRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Entrada não encontrada: " + id));
        return toDTO(entry);
    }

    @Transactional(readOnly = true)
    public Page<TreasuryEntryDTO> findByCashAccountId(UUID cashAccountId, Pageable pageable) {
        return treasuryEntryRepository.findByCashAccountId(cashAccountId, pageable)
                .map(this::toDTO);
    }

    @Transactional(readOnly = true)
    public Page<TreasuryEntryDTO> findByType(TreasuryEntryType type, Pageable pageable) {
        return treasuryEntryRepository.findByType(type, pageable)
                .map(this::toDTO);
    }

    @Transactional(readOnly = true)
    public Page<TreasuryEntryDTO> findByStatus(String status, Pageable pageable) {
        return treasuryEntryRepository.findByStatus(status, pageable)
                .map(this::toDTO);
    }

    @Transactional(readOnly = true)
    public BigDecimal calculateAvailableBalance(UUID cashAccountId) {
        return cashAccountService.getAvailableBalance(cashAccountId);
    }

    /**
     * Atualiza o saldo da conta de caixa baseado no tipo de entrada.
     */
    private void updateCashAccountBalance(TreasuryEntry entry) {
        if (entry.getStatus().equals("EXECUTED")) {
            if (isOutflowType(entry.getType())) {
                cashAccountService.updateBalance(entry.getCashAccount().getId(), entry.getAmount(), "OUTFLOW");
            } else if (isInflowType(entry.getType())) {
                cashAccountService.updateBalance(entry.getCashAccount().getId(), entry.getAmount(), "INFLOW");
            }
        } else if (entry.getStatus().equals("APPROVED") && isOutflowType(entry.getType())) {
            // Comprometer saldo quando aprovado
            cashAccountService.commitBalance(entry.getCashAccount().getId(), entry.getAmount());
        }
    }

    private boolean isOutflowType(TreasuryEntryType type) {
        return type == TreasuryEntryType.PAYMENT_EXECUTION ||
               type == TreasuryEntryType.BATCH_EXECUTION ||
               type == TreasuryEntryType.CASH_WITHDRAWAL ||
               type == TreasuryEntryType.PAYMENT_CANCELLATION;
    }

    private boolean isInflowType(TreasuryEntryType type) {
        return type == TreasuryEntryType.CASH_DEPOSIT ||
               type == TreasuryEntryType.BANK_AVAILABILITY ||
               type == TreasuryEntryType.CASH_AVAILABILITY;
    }

    private TreasuryEntryDTO toDTO(TreasuryEntry entry) {
        return TreasuryEntryDTO.builder()
                .id(entry.getId())
                .type(entry.getType())
                .amount(entry.getAmount())
                .transactionDate(entry.getTransactionDate())
                .documentReference(entry.getDocumentReference())
                .description(entry.getDescription())
                .status(entry.getStatus())
                .approvalLevel(entry.getApprovalLevel())
                .approvedBy(entry.getApprovedBy())
                .approvedAt(entry.getApprovedAt())
                .cashAccountId(entry.getCashAccount().getId())
                .paymentOrderId(entry.getPaymentOrderId())
                .paymentBatchId(entry.getPaymentBatchId())
                .budgetLineId(entry.getBudgetLineId())
                .createdAt(entry.getCreatedAt())
                .updatedAt(entry.getUpdatedAt())
                .build();
    }
}

