/*
 * Handles creating, updating, and validating discount coupons
 * that can be applied to sales.
 *
 * Author: Harkamal
 * Date: April 2026
 */
package com.petshop.backend.service;

import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.common.CouponRequest;
import com.petshop.backend.dto.common.CouponResponse;
import com.petshop.backend.entity.Coupon;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.CouponRepository;
import com.petshop.backend.repository.SaleRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class CouponService {

    private final CouponRepository couponRepository;
    private final SaleRepository saleRepository;

    public CouponService(CouponRepository couponRepository, SaleRepository saleRepository) {
        this.couponRepository = couponRepository;
        this.saleRepository = saleRepository;
    }

    @Transactional(readOnly = true)
    public Page<CouponResponse> getAllCoupons(String query, Boolean active, Pageable pageable) {
        return couponRepository.searchCoupons(query, active, pageable).map(this::mapToResponse);
    }

    @Transactional(readOnly = true)
    public CouponResponse getCouponById(Long id) {
        Coupon coupon = couponRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));
        return mapToResponse(coupon);
    }

    @Transactional(readOnly = true)
    public CouponResponse getCouponByCode(String code) {
        Coupon coupon = couponRepository.findByCouponCode(code)
                .orElseThrow(() -> new ResourceNotFoundException("Coupon not found with code: " + code));
        return mapToResponse(coupon);
    }

    @Transactional
    public CouponResponse createCoupon(CouponRequest request) {
        if (couponRepository.findByCouponCode(request.getCouponCode()).isPresent()) {
            throw new BusinessException("Coupon code already exists: " + request.getCouponCode());
        }

        Coupon coupon = new Coupon();
        updateCouponFields(coupon, request);
        coupon = couponRepository.save(coupon);
        return mapToResponse(coupon);
    }

    @Transactional
    public CouponResponse updateCoupon(Long id, CouponRequest request) {
        Coupon coupon = couponRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));

        couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> {
            if (!existing.getCouponId().equals(id)) {
                throw new BusinessException("Coupon code already exists: " + request.getCouponCode());
            }
        });

        updateCouponFields(coupon, request);
        coupon = couponRepository.save(coupon);
        return mapToResponse(coupon);
    }

    @Transactional
    public void deleteCoupon(Long id) {
        if (!couponRepository.existsById(id)) {
            throw new ResourceNotFoundException("Coupon not found with id: " + id);
        }
        couponRepository.deleteById(id);
    }

    @Transactional
    public void bulkDeleteCoupons(BulkDeleteRequest request) {
        couponRepository.deleteAllById(request.getIds());
    }

    private void updateCouponFields(Coupon coupon, CouponRequest request) {
        if (isPercentageType(request.getDiscountType())
                && request.getDiscountValue().compareTo(new BigDecimal("100")) >= 0) {
            throw new BusinessException("Percentage discount must be less than 100");
        }
        coupon.setCouponCode(request.getCouponCode());
        coupon.setDiscountType(request.getDiscountType());
        coupon.setDiscountValue(request.getDiscountValue());
        coupon.setMinOrderAmount(request.getMinOrderAmount());
        coupon.setActive(request.getActive());
        coupon.setStartsAt(request.getStartsAt());
        coupon.setEndsAt(request.getEndsAt());
        coupon.setUsageLimit(request.getUsageLimit());
    }

    public void validateCouponForUse(Coupon coupon) {
        if (!Boolean.TRUE.equals(coupon.getActive())) {
            throw new BusinessException("Coupon is no longer active");
        }
        LocalDateTime now = LocalDateTime.now();
        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.getUsageLimit() != null) {
            long used = saleRepository.countByCoupon_CouponId(coupon.getCouponId());
            if (used >= coupon.getUsageLimit()) {
                throw new BusinessException("Coupon usage limit has been reached");
            }
        }
    }

    public BigDecimal calculateDiscount(Coupon coupon, BigDecimal subtotal) {
        if (coupon == null || subtotal.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        if (coupon.getMinOrderAmount() != null && subtotal.compareTo(coupon.getMinOrderAmount()) < 0) {
            return BigDecimal.ZERO;
        }

        BigDecimal discount = BigDecimal.ZERO;
        String type = coupon.getDiscountType();
        if (isPercentageType(type)) {
            discount = subtotal.multiply(coupon.getDiscountValue()
                    .divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP));
        } else if (isFixedType(type)) {
            discount = coupon.getDiscountValue();
        }

        return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
    }

    public static boolean isPercentageType(String discountType) {
        if (discountType == null) return false;
        String type = discountType.trim();
        return "PERCENTAGE".equalsIgnoreCase(type) || "PERCENT".equalsIgnoreCase(type);
    }

    public static boolean isFixedType(String discountType) {
        if (discountType == null) return false;
        String type = discountType.trim();
        return "FIXED".equalsIgnoreCase(type) || "FLAT".equalsIgnoreCase(type);
    }

    private CouponResponse mapToResponse(Coupon coupon) {
        return new CouponResponse(
                coupon.getCouponId(),
                coupon.getCouponCode(),
                coupon.getDiscountType(),
                coupon.getDiscountValue(),
                coupon.getMinOrderAmount(),
                coupon.getActive(),
                coupon.getStartsAt(),
                coupon.getEndsAt(),
                coupon.getUsageLimit(),
                coupon.getCreatedAt(),
                coupon.getUpdatedAt()
        );
    }
}
