package com.petshop.backend.service;

import com.petshop.backend.dto.cart.*;
import com.petshop.backend.dto.sale.SaleItemRequest;
import com.petshop.backend.dto.sale.SaleRequest;
import com.petshop.backend.entity.*;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.*;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import com.stripe.param.PaymentIntentCreateParams;
import jakarta.annotation.PostConstruct;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

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

@Service
public class CartService {

    private static final int LOYALTY_POINTS_PER_DOLLAR = 20;

    private final CartRepository cartRepository;
    private final CartItemRepository cartItemRepository;
    private final UserRepository userRepository;
    private final StoreRepository storeRepository;
    private final ProductRepository productRepository;
    private final CouponRepository couponRepository;
    private final SaleRepository saleRepository;
    private final SaleService saleService;

    @Value("${stripe.secret-key:}")
    private String stripeSecretKey;

    public CartService(CartRepository cartRepository,
                       CartItemRepository cartItemRepository,
                        UserRepository userRepository,
                        StoreRepository storeRepository,
                        ProductRepository productRepository,
                        CouponRepository couponRepository,
                        SaleRepository saleRepository,
                        SaleService saleService) {
        this.cartRepository = cartRepository;
        this.cartItemRepository = cartItemRepository;
        this.userRepository = userRepository;
        this.storeRepository = storeRepository;
        this.productRepository = productRepository;
        this.couponRepository = couponRepository;
        this.saleRepository = saleRepository;
        this.saleService = saleService;
    }

    @PostConstruct
    public void initStripe() {
        if (stripeSecretKey != null && !stripeSecretKey.isBlank()) {
            Stripe.apiKey = stripeSecretKey;
        }
    }

    public CartResponse getActiveCart(Long userId, Long storeId) {
        return cartRepository
                .findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .map(this::toResponse)
                .orElse(null);
    }

    @Transactional
    public CartResponse addItem(Long userId, AddToCartRequest request) {
        User user = userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User not found"));
        StoreLocation store = storeRepository.findById(request.getStoreId()).orElseThrow(() -> new ResourceNotFoundException("Store not found"));
        Product product = productRepository.findById(request.getProdId()).orElseThrow(() -> new ResourceNotFoundException("Product not found"));

        if (request.getQuantity() < 1) {
            throw new BusinessException("Quantity must be at least 1");
        }

        Cart cart = cartRepository
                .findActiveCartByUserAndStore(userId, request.getStoreId(), "ACTIVE")
                .orElseGet(() -> {
                    Cart newCart = new Cart();
                    newCart.setUser(user);
                    newCart.setStore(store);
                    newCart.setCartStatus("ACTIVE");
                    newCart.setPointsApplied(false);
                    newCart.setPointsDiscountAmount(BigDecimal.ZERO);

                    return cartRepository.save(newCart);
                });

        requireNotCheckoutPending(cart);

        cartItemRepository.findByCartCartIdAndProductProdId(cart.getCartId(), product.getProdId())
                .ifPresentOrElse(
                        existing -> existing.setQuantity(existing.getQuantity() + request.getQuantity()),
                        () -> {
                            CartItem item = new CartItem();
                            item.setCart(cart);
                            item.setProduct(product);
                            item.setQuantity(request.getQuantity());
                            item.setUnitPrice(product.getProdPrice());
                            cartItemRepository.save(item);
                        }
                );

        recalculate(cart);

        return toResponse(cart);
    }

    @Transactional
    public CartResponse updateItem(Long userId, UpdateCartItemRequest request) {
        CartItem item = cartItemRepository.findById(request.getCartItemId())
                .orElseThrow(() -> new ResourceNotFoundException("Cart item not found"));

        if (!item.getCart().getUser().getId().equals(userId)) {
            throw new BusinessException("Access denied");
        }

        if (!item.getCart().getCartStatus().equals("ACTIVE")) {
            throw new BusinessException("Cart is not active");
        }

        requireNotCheckoutPending(item.getCart());

        if (request.getQuantity() < 1) {
            throw new BusinessException("Quantity must be at least 1");
        }

        item.setQuantity(request.getQuantity());
        cartItemRepository.save(item);
        recalculate(item.getCart());

        return toResponse(item.getCart());
    }

    @Transactional
    public CartResponse removeItem(Long userId, Long cartItemId) {
        CartItem item = cartItemRepository.findById(cartItemId)
                .orElseThrow(() -> new ResourceNotFoundException("Cart item not found"));

        if (!item.getCart().getUser().getId().equals(userId)) {
            throw new BusinessException("Access denied");
        }

        if (!item.getCart().getCartStatus().equals("ACTIVE")) {
            throw new BusinessException("Cart is not active");
        }

        requireNotCheckoutPending(item.getCart());

        Cart cart = item.getCart();
        cartItemRepository.delete(item);
        recalculate(cart);

        return toResponse(cart);
    }

    @Transactional
    public void clearCart(Long userId, Long storeId) {
        cartRepository.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .ifPresent(cart -> {
                    requireNotCheckoutPending(cart);
                    cartItemRepository.deleteByCartCartId(cart.getCartId());
                    cart.setSubtotalAmount(BigDecimal.ZERO);
                    cart.setDiscountAmount(BigDecimal.ZERO);
                    cart.setPointsDiscountAmount(BigDecimal.ZERO);
                    cart.setTotalAmount(BigDecimal.ZERO);
                    cart.setCoupon(null);
                    cart.setPointsApplied(false);
                    cartRepository.save(cart);
                });
    }

    @Transactional
    public CartResponse applyCoupon(Long userId, Long storeId, String couponCode) {
        Cart cart = cartRepository
                .findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .orElseThrow(() -> new BusinessException("No active cart found"));

        requireNotCheckoutPending(cart);

        Coupon coupon = couponRepository.findByCouponCodeIgnoreCase(couponCode)
                .orElseThrow(() -> new BusinessException("Invalid coupon code"));

        LocalDateTime now = LocalDateTime.now();

        if (!coupon.getActive()) {
            throw new BusinessException("Coupon is no longer active");
        }

        if (coupon.getStartsAt() != null && now.isBefore(coupon.getStartsAt())) {
            throw new BusinessException("Coupon is not yet valid");
        }

        if (coupon.getEndsAt() != null && now.isAfter(coupon.getEndsAt())) {
            throw new BusinessException("Coupon has expired");
        }

        if (coupon.getMinOrderAmount() != null && cart.getSubtotalAmount().compareTo(coupon.getMinOrderAmount()) < 0) {
            throw new BusinessException("Minimum order amount of $" + coupon.getMinOrderAmount() + " required");
        }

        if (coupon.getUsageLimit() != null) {
            long used = saleRepository.countByCoupon_CouponId(coupon.getCouponId());
            if (used >= coupon.getUsageLimit()) {
                throw new BusinessException("Coupon usage limit has been reached");
            }
        }

        cart.setCoupon(coupon);
        recalculate(cart);

        return toResponse(cart);
    }

    @Transactional
    public CartResponse removeCoupon(Long userId, Long storeId) {
        Cart cart = cartRepository
                .findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .orElseThrow(() -> new BusinessException("No active cart found"));

        requireNotCheckoutPending(cart);
        cart.setCoupon(null);
        recalculate(cart);

        return toResponse(cart);
    }

    @Transactional
    public CartResponse applyPoints(Long userId, Long storeId, Boolean useLoyaltyPoints) {
        Cart cart = cartRepository
                .findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .orElseThrow(() -> new BusinessException("No active cart found"));

        requireNotCheckoutPending(cart);
        cart.setPointsApplied(Boolean.TRUE.equals(useLoyaltyPoints));
        recalculate(cart);
        return toResponse(cart);
    }

    @Transactional
    public CheckoutResponse checkout(Long userId, Long storeId) {
        Cart cart = cartRepository
                .findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .orElseThrow(() -> new BusinessException("No active cart found"));

        if (Boolean.TRUE.equals(cart.getCheckoutPending())) {
            throw new BusinessException("Checkout already in progress for this cart");
        }

        List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
        if (items.isEmpty()) {
            throw new BusinessException("Cart is empty");
        }

        long amountInCents = cart.getTotalAmount()
                .multiply(BigDecimal.valueOf(100))
                .setScale(0, RoundingMode.HALF_UP)
                .longValue();

        // Free checkout: total is $0.00, or points are applied and the remaining
        // amount is below Stripe's $0.50 minimum (cannot be charged via card)
        if (amountInCents == 0 || (amountInCents < 50 && Boolean.TRUE.equals(cart.getPointsApplied()))) {
            SaleRequest saleRequest = new SaleRequest();
            saleRequest.setStoreId(cart.getStore().getStoreId());
            saleRequest.setCustomerId(cart.getUser().getId());
            saleRequest.setCartId(cart.getCartId());
            saleRequest.setCouponId(cart.getCoupon() != null ? cart.getCoupon().getCouponId() : null);
            saleRequest.setPaymentMethod("Points");
            saleRequest.setChannel("WEBSITE");
            saleRequest.setItems(items.stream()
                    .map(item -> {
                        SaleItemRequest sir = new SaleItemRequest();
                        sir.setProdId(item.getProduct().getProdId());
                        sir.setQuantity(item.getQuantity());
                        return sir;
                    })
                    .toList());

            saleService.createSale(saleRequest);

            cart.setCartStatus("CHECKED_OUT");
            cart.setCheckoutPending(false);
            cartRepository.save(cart);

            return new CheckoutResponse(cart.getCartId(), null, BigDecimal.ZERO, "succeeded");
        }

        if (amountInCents < 50) {
            throw new BusinessException("Order total is too low to process payment");
        }

        try {
            PaymentIntentCreateParams params = PaymentIntentCreateParams.builder()
                    .setAmount(amountInCents)
                    .setCurrency("usd")
                    .setAutomaticPaymentMethods(
                            PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
                                    .setEnabled(true)
                                    .build())
                    .putMetadata("cartId", String.valueOf(cart.getCartId()))
                    .putMetadata("userId", String.valueOf(userId))
                    .build();

            PaymentIntent intent = PaymentIntent.create(params);

            cart.setCheckoutPending(true);
            cart.setCheckoutAmount(cart.getTotalAmount());
            cart.setCheckoutStartedAt(LocalDateTime.now());
            cart.setCheckoutPaymentIntentId(intent.getId());
            cartRepository.save(cart);

            return new CheckoutResponse(
                    cart.getCartId(),
                    intent.getClientSecret(),
                    cart.getTotalAmount(),
                    intent.getStatus()
            );

        }

        catch (StripeException e) {
            throw new BusinessException("Payment processing failed: " + e.getMessage());
        }
    }

    @Transactional
    public void completeCheckout(Long userId, String paymentIntentId) {
        try {
            PaymentIntent intent = PaymentIntent.retrieve(paymentIntentId);

            if (!"succeeded".equals(intent.getStatus())) {
                throw new BusinessException("Payment has not been completed");
            }

            String cartIdMetadata = intent.getMetadata() != null ? intent.getMetadata().get("cartId") : null;
            String userIdMetadata = intent.getMetadata() != null ? intent.getMetadata().get("userId") : null;

            if (cartIdMetadata == null || cartIdMetadata.isBlank()) {
                throw new BusinessException("Payment metadata is missing cart information");
            }
            if (userIdMetadata == null || userIdMetadata.isBlank()) {
                throw new BusinessException("Payment metadata is missing user information");
            }

            Long cartId;
            Long metadataUserId;
            try {
                cartId = Long.parseLong(cartIdMetadata);
                metadataUserId = Long.parseLong(userIdMetadata);
            } catch (NumberFormatException ex) {
                throw new BusinessException("Payment metadata is invalid");
            }

            if (!metadataUserId.equals(userId)) {
                throw new BusinessException("Unauthorized");
            }

            Cart cart = cartRepository.findByIdForUpdate(cartId)
                    .orElseThrow(() -> new BusinessException("Cart not found"));

            if (!cart.getUser().getId().equals(userId)) {
                throw new BusinessException("Unauthorized");
            }

            if (!Boolean.TRUE.equals(cart.getCheckoutPending())) {
                throw new BusinessException("Cart checkout was not initiated");
            }

            if (!paymentIntentId.equals(cart.getCheckoutPaymentIntentId())) {
                throw new BusinessException("Payment intent mismatch");
            }

            if (cart.getCheckoutAmount() == null) {
                throw new BusinessException("Checkout amount snapshot is missing");
            }

            if (!cart.getCartStatus().equals("ACTIVE")) {
                throw new BusinessException("Cart is not in active state");
            }

            List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
            if (items.isEmpty()) {
                throw new BusinessException("Cart items were removed during checkout");
            }

            BigDecimal recalculatedTotal = recalculateTotalAmount(cart);
            if (recalculatedTotal.compareTo(cart.getCheckoutAmount()) != 0) {
                throw new BusinessException("Cart total changed during checkout");
            }

            long storedAmountInCents = cart.getCheckoutAmount()
                    .multiply(BigDecimal.valueOf(100))
                    .setScale(0, RoundingMode.HALF_UP)
                    .longValue();

            if (intent.getAmount() != storedAmountInCents) {
                throw new BusinessException("Stripe charged amount does not match expected amount");
            }

            if (saleRepository.findByCartCartId(cart.getCartId()).isPresent()) {
                cart.setCartStatus("CHECKED_OUT");
                cart.setCheckoutPending(false);
                cart.setCheckoutAmount(null);
                cart.setCheckoutStartedAt(null);
                cart.setCheckoutPaymentIntentId(null);
                cartRepository.save(cart);
                return;
            }

            SaleRequest saleRequest = new SaleRequest();
            saleRequest.setStoreId(cart.getStore().getStoreId());
            saleRequest.setCustomerId(cart.getUser().getId());
            saleRequest.setCartId(cart.getCartId());
            saleRequest.setCouponId(cart.getCoupon() != null ? cart.getCoupon().getCouponId() : null);
            saleRequest.setPaymentMethod("Card");
            saleRequest.setChannel("WEBSITE");
            saleRequest.setItems(cartItemRepository.findByCartCartId(cart.getCartId()).stream()
                    .map(item -> {
                        SaleItemRequest saleItemRequest = new SaleItemRequest();
                        saleItemRequest.setProdId(item.getProduct().getProdId());
                        saleItemRequest.setQuantity(item.getQuantity());
                        return saleItemRequest;
                    })
                    .toList());

            saleService.createSale(saleRequest);

            cart.setCartStatus("CHECKED_OUT");
            cart.setCheckoutPending(false);
            cart.setCheckoutAmount(null);
            cart.setCheckoutStartedAt(null);
            cart.setCheckoutPaymentIntentId(null);
            cartRepository.save(cart);

        } catch (StripeException e) {
            throw new BusinessException("Payment verification failed: " + e.getMessage());
        }
    }

    private void requireNotCheckoutPending(Cart cart) {
        if (Boolean.TRUE.equals(cart.getCheckoutPending())) {
            throw new BusinessException("Cannot modify cart while checkout is in progress");
        }
    }

    private void recalculate(Cart cart) {
        List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());

        BigDecimal subtotal = items.stream()
                .map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        cart.setSubtotalAmount(subtotal);

        BigDecimal discount = BigDecimal.ZERO;
        Coupon coupon = cart.getCoupon();

        if (coupon != null) {
            if (isPercentageType(coupon.getDiscountType())) {
                discount = subtotal.multiply(coupon.getDiscountValue())
                        .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
            } else if (isFixedType(coupon.getDiscountType())) {
                discount = coupon.getDiscountValue().min(subtotal);
            }
        }

        discount = discount.max(BigDecimal.ZERO).min(subtotal);
        cart.setDiscountAmount(discount);

        BigDecimal remainingAfterCoupon = subtotal.subtract(discount).max(BigDecimal.ZERO);
        BigDecimal pointsDiscount = calculatePointsDiscount(cart.getUser(), remainingAfterCoupon, Boolean.TRUE.equals(cart.getPointsApplied()));
        cart.setPointsDiscountAmount(pointsDiscount);
        cart.setTotalAmount(remainingAfterCoupon.subtract(pointsDiscount).max(BigDecimal.ZERO));
        cartRepository.save(cart);
    }

    private BigDecimal recalculateTotalAmount(Cart cart) {
        List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());

        BigDecimal subtotal = items.stream()
                .map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        BigDecimal discount = BigDecimal.ZERO;
        Coupon coupon = cart.getCoupon();

        if (coupon != null) {
            if (isPercentageType(coupon.getDiscountType())) {
                discount = subtotal.multiply(coupon.getDiscountValue())
                        .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
            } else if (isFixedType(coupon.getDiscountType())) {
                discount = coupon.getDiscountValue().min(subtotal);
            }
        }

        discount = discount.max(BigDecimal.ZERO).min(subtotal);

        BigDecimal remainingAfterCoupon = subtotal.subtract(discount).max(BigDecimal.ZERO);
        BigDecimal pointsDiscount = calculatePointsDiscount(cart.getUser(), remainingAfterCoupon, Boolean.TRUE.equals(cart.getPointsApplied()));

        return remainingAfterCoupon.subtract(pointsDiscount).max(BigDecimal.ZERO);
    }

    private boolean isPercentageType(String discountType) {
        return "PERCENTAGE".equalsIgnoreCase(discountType) || "PERCENT".equalsIgnoreCase(discountType);
    }

    private boolean isFixedType(String discountType) {
        return "FIXED".equalsIgnoreCase(discountType) || "FLAT".equalsIgnoreCase(discountType);
    }


    private BigDecimal calculatePointsDiscount(User user, BigDecimal remainingAmount, boolean pointsApplied) {
        if (!pointsApplied || user == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }

        int availablePoints = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0;
        int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR;
        if (wholeDollars <= 0) {
            return BigDecimal.ZERO;
        }

        BigDecimal maxDiscount = BigDecimal.valueOf(wholeDollars);
        // If points can fully cover the remaining amount, discount the entire total to $0.00
        if (maxDiscount.compareTo(remainingAmount) >= 0) {
            return remainingAmount.setScale(2, RoundingMode.HALF_UP);
        }
        return maxDiscount.setScale(2, RoundingMode.HALF_UP);
    }

    private CartResponse toResponse(Cart cart) {
        List<CartItemResponse> itemResponses = cartItemRepository
                .findByCartCartId(cart.getCartId())
                .stream()
                .map(item -> {
                    String rawImageUrl = item.getProduct().getImageUrl();
                    String imageUrl = (rawImageUrl != null && !rawImageUrl.isBlank())
                            ? "/api/v1/products/" + item.getProduct().getProdId() + "/image"
                            : null;
                    return new CartItemResponse(
                            item.getCartItemId(),
                            item.getProduct().getProdId(),
                            item.getProduct().getProdName(),
                            imageUrl,
                            item.getUnitPrice(),
                            item.getQuantity(),
                            item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity()))
                    );
                })
                .toList();

        CartResponse response = new CartResponse();
        response.setCartId(cart.getCartId());
        response.setStoreId(cart.getStore() != null ? cart.getStore().getStoreId() : null);
        response.setCartStatus(cart.getCartStatus());
        response.setItems(itemResponses);
        response.setSubtotalAmount(cart.getSubtotalAmount());
        response.setDiscountAmount(cart.getDiscountAmount());
        response.setPointsDiscountAmount(cart.getPointsDiscountAmount());
        response.setTotalAmount(cart.getTotalAmount());
        response.setCouponCode(cart.getCoupon() != null ? cart.getCoupon().getCouponCode() : null);
        response.setCouponDiscountType(cart.getCoupon() != null ? cart.getCoupon().getDiscountType() : null);
        response.setCouponDiscountValue(cart.getCoupon() != null ? cart.getCoupon().getDiscountValue() : null);
        response.setPointsApplied(cart.getPointsApplied());
        response.setAvailableLoyaltyPoints(cart.getUser() != null ? cart.getUser().getLoyaltyPoints() : null);
        response.setCheckoutPending(cart.getCheckoutPending());

        return response;
    }

    @Transactional
    public void cancelCheckout(Long userId, Long storeId) {
        cartRepository.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
                .ifPresent(cart -> {
                    if (!Boolean.TRUE.equals(cart.getCheckoutPending())) {
                        return;
                    }
                    cart.setCheckoutPending(false);
                    cart.setCheckoutAmount(null);
                    cart.setCheckoutStartedAt(null);
                    cart.setCheckoutPaymentIntentId(null);
                    cartRepository.save(cart);
                });
    }
}
