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.nocopy.dto.payout.AddBankTransferRequest;
import ru.soune.nocopy.dto.payout.AddCardRequest;
import ru.soune.nocopy.dto.payout.PayoutMethodDTO;
import ru.soune.nocopy.entity.payout.*;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.DuplicateMethodException;
import ru.soune.nocopy.repository.PayoutMethodRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.user.UserService;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class PayoutMethodService {

    private final PayoutMethodRepository payoutMethodRepository;

    private final UserRepository userRepository;

    @Transactional
    public void initiatedStandartPayoutMethods(User user) {
        createInternalMethod(user);
    }

    @Transactional
    public InternalPayoutMethod createInternalMethod(User user) {
        if (payoutMethodRepository.findByUserAndType(user.getId(), InternalPayoutMethod.class).isPresent()) {
            return (InternalPayoutMethod) payoutMethodRepository.findByUserAndType(
                    user.getId(), InternalPayoutMethod.class).get();
        }

        InternalPayoutMethod internalMethod = new InternalPayoutMethod();
        internalMethod.setUser(user);
        internalMethod.setDefault(true);

        return payoutMethodRepository.save(internalMethod);
    }

    public List<PayoutMethodDTO> getUserMethods(long userId) {
        return payoutMethodRepository.findByUserId(userId)
                .stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    @Transactional
    public void setDefaultMethod(Long userId, Long methodId) {
        payoutMethodRepository.clearOtherDefault(userId, methodId);

        PayoutMethod method = payoutMethodRepository.findByIdAndUserId(methodId, userId)
                .orElseThrow(() -> new IllegalArgumentException("Not found method"));
        method.setDefault(true);

        payoutMethodRepository.save(method);
    }

    @Transactional
    public BankTransferPayoutMethod addBankTransferMethod(Long userId, AddBankTransferRequest request) {
        User user = userRepository.findById(userId).orElseThrow();

        BankTransferPayoutMethod bankMethod = new BankTransferPayoutMethod();
        bankMethod.setUser(user);
        bankMethod.setBankName(request.getBankName());
        bankMethod.setBic(request.getBic());
        bankMethod.setCorrespondentAccount(request.getCorrespondentAccount());
        bankMethod.setAccountNumber(request.getAccountNumber());
        bankMethod.setAccountHolder(request.getAccountHolder());
        bankMethod.setInn(request.getInn());
        bankMethod.setKpp(request.getKpp());
        bankMethod.setDefault(payoutMethodRepository.countByUserId(user.getId()) == 0);

        return payoutMethodRepository.save(bankMethod);
    }

    @Transactional
    public CardPayoutMethod addCardMethod(Long userId, AddCardRequest request) {
        List<PayoutMethod> byUserId = payoutMethodRepository.findByUserId(userId);

        if (!byUserId.isEmpty()) {
            for (PayoutMethod payoutMethod : byUserId) {
                if (payoutMethod.getMethodType().equals(PayoutType.CARD.getDisplayName().toUpperCase())) {
                    payoutMethodRepository.delete(payoutMethod);
                }
            }
        }

        String cardHash = generateCardHash(request.getCardNumber());

        if (payoutMethodRepository.existsByCardHash(cardHash)) {
            throw new DuplicateMethodException("Card was added");
        }

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

        CardPayoutMethod cardMethod = new CardPayoutMethod();
        cardMethod.setUser(user);
        cardMethod.setCardNumber(request.getCardNumber());
        cardMethod.setCardHash(cardHash);
        cardMethod.setDefault(payoutMethodRepository.countByUserId(user.getId()) == 0);

        CardPayoutMethod saved = payoutMethodRepository.save(cardMethod);
        log.info("Added user card: {}", user.getId());

        return saved;
    }

    public PayoutMethod getDefaultMethod(User user) {
        return payoutMethodRepository.findByUserIdAndIsDefaultTrue(user.getId())
                .orElseGet(() -> {
                    List<PayoutMethod> methods = payoutMethodRepository.findByUserId(user.getId());
                    if (methods.isEmpty()) {
                        return createInternalMethod(user);
                    }
                    return methods.get(0);
                });
    }

    public PayoutMethodDTO convertToDto(PayoutMethod method) {
        PayoutMethodDTO payoutMethodDTO = PayoutMethodDTO.builder()
                .id(method.getId())
                .type(method.getClass().getSimpleName().replace("PayoutMethod", ""))
                .displayName(method.getDisplayName())
                .maskedDetails(method.getMaskedDetails())
                .isDefault(method.isDefault())
                .build();

        if (method instanceof CardPayoutMethod cardMethod) {
            payoutMethodDTO.setCardNumber(cardMethod.getCardNumber());
        }

        return payoutMethodDTO;
    }

    private String generateCardHash(String cardNumber) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hash = md.digest(cardNumber.getBytes());
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed generate hash", e);
        }
    }
}
