package com.fsd.hellovelo.service;

import com.fsd.hellovelo.entity.*;
import com.fsd.hellovelo.exceptions.ResourceNotFoundException;
import com.fsd.hellovelo.payload.request.UpdateProfileRequest;
import com.fsd.hellovelo.payload.response.SubscriptionAndInvoiceResponse;
import com.fsd.hellovelo.repository.InvoiceRepository;
import com.fsd.hellovelo.repository.RoleRepository;
import com.fsd.hellovelo.repository.SubscriptionRepository;
import com.fsd.hellovelo.repository.UserRepository;
import com.fsd.hellovelo.security.services.UserDetailsImpl;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.PromotionCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PutMapping;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class ProfileService {

    private final UserRepository userRepository;
    private final SubscriptionRepository subscriptionRepository;
    private final InvoiceRepository invoiceRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    public ProfileService(UserRepository userRepository, SubscriptionRepository subscriptionRepository, InvoiceRepository invoiceRepository){
        this.userRepository = userRepository;
        this.subscriptionRepository = subscriptionRepository;
        this.invoiceRepository = invoiceRepository;
    }

    public User getUser() {
        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Long userId = userDetails.getId();
        return userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("Error: User " + userId + " not found."));
    }

    public User updateProfile(UpdateProfileRequest userInput) {
        UserDetailsImpl authUser = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Long userId = authUser.getId();

        if(!authUser.getUsername().equals(userInput.getUsername())) {
            Optional <User> byUsername = userRepository.findByUsername(userInput.getUsername());
            if(byUsername.isPresent()) {
                throw new IllegalArgumentException("Error: Username already taken.");
            }
        }
        User user = userRepository.findById(userId).get();
        System.out.println(user.getEmail());
        if(!user.getEmail().equals(userInput.getEmail())) {
            System.out.println("here");
            Optional<User> byEmail = userRepository.findByEmail(userInput.getEmail());
            if(byEmail.isPresent()) {
                throw new IllegalArgumentException("Error: Email already in use");
            }
        }
        user.setEmail(userInput.getEmail());
        user.setUsername(userInput.getUsername());
        user.setLastName(userInput.getLastName());
        user.setFirstName(userInput.getFirstName());

        if(userInput.getPassword().length() != 0) {
            if(!user.getPassword().equals(userInput.getPassword())) {
                user.setPassword(encoder.encode(userInput.getPassword()));
            }
        }

        return userRepository.save(user);
    }

    public void deactivateAccount() {
        UserDetailsImpl authUser = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Long userId = authUser.getId();
        User user = userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User with id " + userId + " not found."));
        user.setStatus(EStatus.DISABLED);
        userRepository.save(user);

    }

    public SubscriptionAndInvoiceResponse getSubAndInvoiceDetails() throws StripeException {
        SubscriptionAndInvoiceResponse response = new SubscriptionAndInvoiceResponse();

        Stripe.apiKey = "sk_test_51K6PnvAl7XnbPt50bAvU4L3nGTDI8nXn8a4nPhjTcbznNDfW3j7sBL1UqmL2x5yUVXygbNJ6o32Gg01juu76NkbJ00ereY9xEJ";
        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        User user = userRepository.findById(userDetails.getId()).orElseThrow(() -> new ResourceNotFoundException("Error: User not found."));

        Subscription subscription;
        Optional<com.fsd.hellovelo.entity.Subscription> subscriptionOptional = subscriptionRepository.findFirstByUserAndTypeEqualsAndStatusNot(user, EType.BIKE, EPaymentStatus.CANCELED);

        subscription = subscriptionOptional.orElseGet(() -> subscriptionRepository.findFirstByUserAndTypeEqualsAndStatus(user, EType.BIKE, EPaymentStatus.CANCELED).orElseThrow(() -> new ResourceNotFoundException("No subscriptions found.")));

        Optional<Invoice> invoiceOptional = invoiceRepository.findBySubscriptionAndInvoiceStatusNot(subscription, EInvoiceStatus.PAID);
        Invoice invoice;
        if(invoiceOptional.isEmpty()){
            invoice = invoiceRepository.findFirstBySubscriptionAndInvoiceStatusOrderByIdDesc(subscription, EInvoiceStatus.PAID).orElseThrow(() -> new ResourceNotFoundException("No invoices found."));
        }else{
            invoice = invoiceOptional.get();
        }

        BigDecimal basePrice = subscription.getPrice().divide(BigDecimal.valueOf(100));
        response.setBasePrice("$" + String.format("%.2f",basePrice));

        response.setOveragePrice("$3 for each 30 minutes over 45 minutes");

        List<String> appliedPromos = new ArrayList<>();
        for(Discount discount : invoice.getAppliedDiscounts()){
            PromotionCode stripePromo = PromotionCode.retrieve(discount.getStripePromoCodeId());
            com.stripe.model.Coupon coupon = stripePromo.getCoupon();
            String promo = (coupon.getName() == null ? "Coupon" : coupon.getName()) + " - " + (coupon.getAmountOff() == null ? coupon.getPercentOff() + "% off" : "$" + String.format("%.2f",(coupon.getAmountOff()/100D)) + " off");
            appliedPromos.add(promo);
        }
        response.setAppliedPromos(appliedPromos);

        response.setOverageMinutes(invoice.getOverageSum());

        long fees = ((Double)Math.ceil(invoice.getOverageSum() / 30D)).longValue() * 300;
        response.setOverageFees("$" + String.format("%.2f", (fees / 100D)));

        response.setRentalStationDiscounts((invoice.getRentalDiscountSum() == 0 ? "" : "-") + "$" + String.format("%.2f", (invoice.getRentalDiscountSum() / 100D)));

        List<Invoice> invoiceList = invoiceRepository.findBySubscriptionAndInvoiceStatusOrderByDateDue(subscription, EInvoiceStatus.PAID);
        List<String> invoiceLinks = new ArrayList<>();
        for(Invoice pastInvoice: invoiceList){
            if(pastInvoice.getInvoicePdfUrl() != null){
                invoiceLinks.add(pastInvoice.getInvoicePdfUrl());
            }
        }
        response.setPreviousInvoicePdfLink(invoiceLinks);

        if(invoice.getInvoiceStatus() == EInvoiceStatus.CREATED){
            if(subscription.getStatus() == EPaymentStatus.CANCELED){
                response.setMessage("Your subscription is being canceled and finalized.");
            }else{
                response.setMessage("Your invoice is finalizing. " + invoice.getInvoiceStatus());
            }
            com.stripe.model.Invoice stripeInvoice = com.stripe.model.Invoice.retrieve(invoice.getStripeInvoiceId());
            response.setCurrentEstimate("$" + String.format("%.2f", (stripeInvoice.getAmountDue() / 100D)));
        }else if(invoice.getInvoiceStatus() == EInvoiceStatus.NOT_CREATED){
            response.setMessage("This invoice will be finalized on the due date.");
            long estimate = invoice.getPaymentPrice().longValue() + fees;
            response.setCurrentEstimate("$" + String.format("%.2f", (estimate / 100D)));
        }else{
            response.setMessage("This invoice is already finalized. You have no upcoming invoices.");
            com.stripe.model.Invoice stripeInvoice = com.stripe.model.Invoice.retrieve(invoice.getStripeInvoiceId());
            response.setCurrentEstimate("$" + String.format("%.2f", (stripeInvoice.getAmountDue() / 100D)));
        }

        return response;
    }
}
