package ru.soune.nocopy.service.tariff;

import jakarta.mail.MessagingException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffStatus;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.repository.TariffInfoRepository;
import ru.soune.nocopy.repository.TariffRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.mail.EmailService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
@Slf4j
public class TariffInfoService {

    private TariffInfoRepository tariffInfoRepository;

    private TariffRepository tariffRepository;

    private UserRepository userRepository;

    private EmailService emailService;

    private TokenOperationService tokenOperationService;

    public TariffInfo createTariffInfo(LocalDateTime startTariff, LocalDateTime endTariff, TariffStatus tariffStatus,
                                       Integer tokens) {
        TariffInfo tariffInfo = new TariffInfo();
        tariffInfo.setStartTariff(startTariff);
        tariffInfo.setStatus(tariffStatus);
        tariffInfo.setEndTariff(endTariff);
        tariffInfo.setTokens(tokens);
        tariffInfo.setBoughtTokens(0);

        return tariffInfoRepository.save(tariffInfo);
    }

//    @Transactional
    public void writeOffTokens(long userId, Integer value, OperationType operationType) throws MessagingException, IOException {
        User user = userRepository.findById(userId).orElseThrow();

        TariffInfo activeTariffInfo;

        if (user.getCompany() == null) {
            activeTariffInfo = user.getPersonalTariffInfo();
        } else {
            activeTariffInfo = user.getCompany().getTariffInfo();
        }

        writeOffTokensForTariff(activeTariffInfo, value);

        tokenOperationService.create(operationType, userId, Long.valueOf(value));
    }

    public void writeOffTokensForTariff(TariffInfo activeTariffInfo, Integer value) throws MessagingException, IOException {
        Integer tokens = activeTariffInfo.getTokens();
        Integer boughtTokens = activeTariffInfo.getBoughtTokens();

        if (tokens - value >= 0) {
            activeTariffInfo.setTokens(activeTariffInfo.getTokens() - value);

            tariffInfoRepository.save(activeTariffInfo);
        } else if (boughtTokens - value >= 0) {
            activeTariffInfo.setTokens(activeTariffInfo.getBoughtTokens() - value);

            tariffInfoRepository.save(activeTariffInfo);
        } else {
            int needTokens = boughtTokens - value;
            User user = userRepository.findByPersonalTariffInfo(activeTariffInfo);

            emailService.sendTokensNotFoundEmail(user, tokens,  Math.abs(needTokens));
            log.error("User:" + user.getId() + " not have tokens for protect");

            throw new TariffNotFoundException("User not have tokens for protect");
        }
    }

    public TariffInfo createTariffInfo(TariffType tariffType) {
        Tariff tariff = tariffRepository.findByType(tariffType.toString()).orElseThrow();

        TariffInfo tariffInfo = createTariffInfo(LocalDateTime.now(),
                LocalDateTime.now().plusDays(tariff.getTariffTerm().getDays()), TariffStatus.ACTIVE, tariff.getTokens());
        tariffInfo.setTariff(tariff);

        return tariffInfoRepository.save(tariffInfo);
    }

    public TariffInfo addTariffInfo(TariffInfo tariffInfo) {
        return tariffInfoRepository.save(tariffInfo);
    }

    public List<TariffInfoDTO> getAllTariffInfos() {
        return tariffInfoRepository.findAll()
                .stream()
                .map(this::convertToInfoDTO)
                .collect(Collectors.toList());
    }

    public TariffInfoDTO getTariffInfoById(String id) {
        return tariffInfoRepository.findById(id)
                .map(this::convertToInfoDTO)
                .orElseThrow(() ->  new TariffNotFoundException("Tariff info not found"));
    }

    @Transactional
    public TariffInfoDTO createTariffInfo(TariffInfoDTO tariffInfoDTO) {
        TariffInfo tariffInfo = new TariffInfo();
        updateTariffInfoFromDTO(tariffInfo, tariffInfoDTO);
        return convertToInfoDTO(tariffInfoRepository.save(tariffInfo));
    }

    @Transactional
    public TariffInfoDTO updateTariffInfo(String id, TariffInfoDTO tariffInfoDTO) {
        TariffInfo tariffInfo = tariffInfoRepository.findById(id)
                .orElseThrow(() -> new TariffNotFoundException("Tariff not found"));

        updateTariffInfoFromDTO(tariffInfo, tariffInfoDTO);

        return convertToInfoDTO(tariffInfoRepository.save(tariffInfo));
    }

    @Transactional
    public void deleteTariffInfo(String id) {
        tariffInfoRepository.deleteById(id);
    }

    private TariffInfoDTO convertToInfoDTO(TariffInfo tariffInfo) {
        return new TariffInfoDTO(
                tariffInfo.getId(),
                tariffInfo.getStatus().name(),
                tariffInfo.getStartTariff(),
                tariffInfo.getEndTariff(),
                tariffInfo.getTariff() != null ? tariffInfo.getTariff().getId() : null,
                tariffInfo.getTariff() != null ? tariffInfo.getTariff().getName() : null,
                tariffInfo.getTokens() + tariffInfo.getBoughtTokens(),
                tariffInfo.getTariff().getDiskSize(), 0L, 0,
                tariffInfo.getTariff().getMaxFilesCount());
    }

    @Transactional
    public void linkUserTariffInfo(User user, TariffInfo tariffInfo) {
        user.setPersonalTariffInfo(tariffInfo);

        userRepository.save(user);
    }

    private void updateTariffInfoFromDTO(TariffInfo tariffInfo, TariffInfoDTO dto) {
        tariffInfo.setStatus(TariffStatus.valueOf(dto.getStatus()));
        tariffInfo.setStartTariff(dto.getStartTariff());
        tariffInfo.setEndTariff(dto.getEndTariff());

        if (dto.getTariffId() != null) {
            Tariff tariff = tariffRepository.findById(dto.getTariffId())
                    .orElseThrow(() ->  new TariffNotFoundException("Tariff not found"));
            tariffInfo.setTariff(tariff);
        }
    }
}
