package ru.soune.nocopy.service.payout;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.ReferralService;
import ru.soune.nocopy.dto.payout.PayoutRequestDTO;
import ru.soune.nocopy.entity.payout.PayoutMethod;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutRequestStatus;
import ru.soune.nocopy.entity.payout.PayoutType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InsufficientFundsException;
import ru.soune.nocopy.exception.InvalidPayoutMethodException;
import ru.soune.nocopy.exception.PendingPayoutExistsException;
import ru.soune.nocopy.repository.PayoutMethodRepository;
import ru.soune.nocopy.repository.PayoutRequestRepository;
import ru.soune.nocopy.repository.UserRepository;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class PayoutRequestService {
    private final PayoutRequestRepository payoutRequestRepository;

    private final PayoutMethodRepository payoutMethodRepository;

    private final BalanceService balanceService;

    private final UserRepository userRepository;

    private static final BigDecimal MIN_PAYOUT_AMOUNT = new BigDecimal("1000.00");

    private static final List<PayoutRequestStatus> BLOCKING_STATUSES = Arrays.asList(PayoutRequestStatus.PENDING,
            PayoutRequestStatus.PROCESSING);

    @Transactional
    public PayoutRequest createPayoutRequest(Long userId, PayoutRequestDTO request) {
        log.info("Create request for money {}: sum {}", userId, request.getAmount());

        validatePayoutAmount(request.getAmount());

        User user = userRepository.findById(userId).orElseThrow();

        if (!balanceService.hasSufficientFunds(user, request.getAmount())) {
            throw new InsufficientFundsException(String.format("Not have money for payout. Have: %.2f",
                            balanceService.getBalance(user)));
        }

        if (hasActivePayoutRequests(user)) {
            throw new PendingPayoutExistsException("Have active payout. Wait for end.");
        }

        PayoutMethod payoutMethod = payoutMethodRepository
                .findByIdAndUserId(request.getPayoutMethodId(), user.getId())
                .orElseThrow(() -> new InvalidPayoutMethodException("Not found payout method"));

        PayoutType payoutType = determinePayoutType(payoutMethod);

        PayoutRequest payoutRequest = PayoutRequest.builder()
                .user(user)
                .payoutMethod(payoutMethod)
                .amount(request.getAmount())
                .status(PayoutRequestStatus.PENDING)
                .payoutType(payoutType)
                .build();

        PayoutRequest savedRequest = payoutRequestRepository.save(payoutRequest);

        balanceService.freezeAmount(user, request.getAmount());

        return savedRequest;
    }

    private void validatePayoutAmount(BigDecimal amount) {
        if (amount == null) {
            throw new IllegalArgumentException("Sum is null");
        }

        if (amount.compareTo(MIN_PAYOUT_AMOUNT) < 0) {
            throw new IllegalArgumentException(
                    String.format("Min sum for payout: %.2f rub.", MIN_PAYOUT_AMOUNT)
            );
        }

    }

    private boolean hasActivePayoutRequests(User user) {
        return payoutRequestRepository.existsByUserIdAndStatusIn(user.getId(), BLOCKING_STATUSES);
    }

    private PayoutType determinePayoutType(PayoutMethod payoutMethod) {
        String className = payoutMethod.getClass().getSimpleName();

        switch (className) {
            case "CardPayoutMethod":
                return PayoutType.CARD;
            case "BankTransferPayoutMethod":
                return PayoutType.BANK_TRANSFER;
            case "InternalPayoutMethod":
                return PayoutType.INTERNAL;
            default:
                throw new InvalidPayoutMethodException("Unknown method");
        }
    }

    @Transactional(readOnly = true)
    public List<PayoutRequest> getUserPayoutRequests(Long userId) {
        return payoutRequestRepository.findByUserIdOrderByCreatedAtDesc(userId);
    }

    @Transactional(readOnly = true)
    public PayoutRequest getPayoutRequest(Long userId, Long requestId) {
        return payoutRequestRepository.findByIdAndUserId(requestId, userId)
                .orElseThrow(() -> new IllegalArgumentException("Payout not found"));
    }

    @Transactional
    public PayoutRequest cancelPayoutRequest(Long userId, Long requestId) {
        PayoutRequest request = getPayoutRequest(userId, requestId);

        if (request.getStatus() != PayoutRequestStatus.PENDING) {
            throw new IllegalStateException(
                    "Can't cnacel: " + request.getStatus()
            );
        }

        request.setStatus(PayoutRequestStatus.CANCELLED);

        balanceService.unfreezeAmount(userId, request.getAmount());

        return payoutRequestRepository.save(request);
    }

    @Transactional
    public PayoutRequest processRequest(Long requestId) {
        PayoutRequest request = payoutRequestRepository.findById(requestId)
                .orElseThrow();

        if (request.getStatus() != PayoutRequestStatus.PENDING) {
            throw new IllegalStateException("Can't proceed in status: " + request.getStatus());
        }

        request.setStatus(PayoutRequestStatus.COMPLETED);
        request.setProcessAt(LocalDateTime.now());

        balanceService.withdrawAmount(request.getUser().getId(), request.getAmount());

        return payoutRequestRepository.save(request);
    }
}
