package com.petshop.backend.service;

import com.petshop.backend.dto.sale.SaleRequest;
import com.petshop.backend.dto.sale.SaleResponse;
import com.petshop.backend.entity.*;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.*;
import com.petshop.backend.util.AuthenticationHelper;
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.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
public class SaleService {

    private final SaleRepository saleRepository;
    private final ProductRepository productRepository;
    private final StoreRepository storeRepository;
    private final InventoryRepository inventoryRepository;
    private final UserRepository userRepository;
    private final CouponRepository couponRepository;
    private final CartRepository cartRepository;

    public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository, CouponRepository couponRepository, CartRepository cartRepository) {
        this.saleRepository = saleRepository;
        this.productRepository = productRepository;
        this.storeRepository = storeRepository;
        this.inventoryRepository = inventoryRepository;
        this.userRepository = userRepository;
        this.couponRepository = couponRepository;
        this.cartRepository = cartRepository;
    }

    @Transactional(readOnly = true)
    public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Boolean isRefund, Pageable pageable) {
        Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, isRefund, pageable);
        return sales.map(this::mapToResponse);
    }

    @Transactional(readOnly = true)
    public SaleResponse getSaleById(Long id) {
        Sale sale = saleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Sale not found with id: " + id));
        return mapToResponse(sale);
    }

    @Transactional
    public SaleResponse createSale(SaleRequest request) {
        User employee = AuthenticationHelper.getAuthenticatedUser(userRepository);

        StoreLocation store = storeRepository.findById(request.getStoreId())
                .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));

        if (employee.getRole() == User.Role.STAFF) {
            Long assignedStoreId = employee.getPrimaryStore() != null ? employee.getPrimaryStore().getStoreId() : null;
            if (!store.getStoreId().equals(assignedStoreId)) {
                throw new BusinessException("Staff can only create sales for their assigned store");
            }
        }

        Sale sale = new Sale();
        sale.setSaleDate(LocalDateTime.now());
        sale.setEmployee(employee);
        sale.setStore(store);
        sale.setPaymentMethod(normalizePaymentMethod(request.getPaymentMethod()));
        sale.setIsRefund(request.getIsRefund() != null ? request.getIsRefund() : false);
        sale.setChannel(request.getChannel() != null ? request.getChannel() : "IN_STORE");

        if (request.getCouponId() != null) {
            Coupon coupon = couponRepository.findById(request.getCouponId())
                    .orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
            sale.setCoupon(coupon);
        }

        if (request.getCartId() != null) {
            Cart cart = cartRepository.findById(request.getCartId())
                    .orElseThrow(() -> new ResourceNotFoundException("Cart not found with id: " + request.getCartId()));
            sale.setCart(cart);
        }

        if (request.getCustomerId() != null) {
            User customer = userRepository.findById(request.getCustomerId())
                    .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
            sale.setCustomer(customer);
        }

        if (sale.getIsRefund() && request.getOriginalSaleId() != null) {
            Sale originalSale = saleRepository.findById(request.getOriginalSaleId())
                    .orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId()));
            sale.setOriginalSale(originalSale);
        }

        BigDecimal totalAmount = BigDecimal.ZERO;
        List<SaleItem> saleItems = new ArrayList<>();

        if (sale.getIsRefund() && sale.getOriginalSale() != null) {
            for (var itemRequest : request.getItems()) {
                Product product = productRepository.findById(itemRequest.getProdId())
                        .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));

                SaleItem originalItem = sale.getOriginalSale().getItems().stream()
                        .filter(item -> item.getProduct().getProdId().equals(itemRequest.getProdId()))
                        .findFirst()
                        .orElseThrow(() -> new BusinessException("Product " + itemRequest.getProdId() + " was not in the original sale"));

                if (itemRequest.getQuantity() > originalItem.getQuantity()) {
                    throw new BusinessException("Refund quantity " + itemRequest.getQuantity() +
                            " exceeds original quantity " + originalItem.getQuantity() +
                            " for product: " + product.getProdName());
                }

                int alreadyRefundedQuantity = saleRepository.findByOriginalSaleSaleId(sale.getOriginalSale().getSaleId()).stream()
                        .flatMap(existingRefund -> existingRefund.getItems().stream())
                        .filter(existingRefundItem -> existingRefundItem.getProduct().getProdId().equals(itemRequest.getProdId()))
                        .mapToInt(existingRefundItem -> Math.abs(existingRefundItem.getQuantity()))
                        .sum();

                int refundableQuantity = originalItem.getQuantity() - alreadyRefundedQuantity;
                if (itemRequest.getQuantity() > refundableQuantity) {
                    throw new BusinessException("Refund quantity " + itemRequest.getQuantity() +
                            " exceeds remaining refundable quantity " + refundableQuantity +
                            " for product: " + product.getProdName());
                }

                Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProdId(), store.getStoreId())
                        .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));

                inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity());
                inventoryRepository.save(inventory);

                BigDecimal unitPrice = originalItem.getUnitPrice();
                BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity()));

                SaleItem saleItem = new SaleItem();
                saleItem.setSale(sale);
                saleItem.setProduct(product);
                saleItem.setQuantity(-itemRequest.getQuantity());
                saleItem.setUnitPrice(unitPrice);

                saleItems.add(saleItem);
                totalAmount = totalAmount.add(itemTotal);
            }
            totalAmount = totalAmount.negate();
        } else {
            for (var itemRequest : request.getItems()) {
                Product product = productRepository.findById(itemRequest.getProdId())
                        .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));

                Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProdId(), store.getStoreId())
                        .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));

                if (inventory.getQuantity() < itemRequest.getQuantity()) {
                    throw new BusinessException("Insufficient stock for product: " + product.getProdName() +
                        ". Available: " + inventory.getQuantity() + ", requested: " + itemRequest.getQuantity());
                }

                inventory.setQuantity(inventory.getQuantity() - itemRequest.getQuantity());
                inventoryRepository.save(inventory);

                BigDecimal unitPrice = product.getProdPrice();
                BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity()));

                SaleItem saleItem = new SaleItem();
                saleItem.setSale(sale);
                saleItem.setProduct(product);
                saleItem.setQuantity(itemRequest.getQuantity());
                saleItem.setUnitPrice(unitPrice);

                saleItems.add(saleItem);
                totalAmount = totalAmount.add(itemTotal);
            }
        }

        sale.setTotalAmount(totalAmount);
        sale.setItems(saleItems);

        Sale savedSale = saleRepository.save(sale);
        return mapToResponse(savedSale);
    }

    private SaleResponse mapToResponse(Sale sale) {
        SaleResponse response = new SaleResponse();
        response.setSaleId(sale.getSaleId());
        response.setSaleDate(sale.getSaleDate());
        response.setEmployeeId(sale.getEmployee().getId());
        response.setEmployeeName(sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName());

        if (sale.getStore() != null) {
            response.setStoreId(sale.getStore().getStoreId());
            response.setStoreName(sale.getStore().getStoreName());
        }

        response.setTotalAmount(sale.getTotalAmount());
        response.setSubtotalAmount(sale.getSubtotalAmount());
        response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
        response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount());
        response.setPointsEarned(sale.getPointsEarned());
        response.setChannel(sale.getChannel());
        if (sale.getCoupon() != null) {
            response.setCouponId(sale.getCoupon().getCouponId());
        }
        if (sale.getCart() != null) {
            response.setCartId(sale.getCart().getCartId());
        }
        response.setPaymentMethod(sale.getPaymentMethod());
        response.setIsRefund(sale.getIsRefund());
        if (sale.getOriginalSale() != null) {
            response.setOriginalSaleId(sale.getOriginalSale().getSaleId());
        }
        response.setCreatedAt(sale.getCreatedAt());

        List<SaleResponse.SaleItemResponse> itemResponses = new ArrayList<>();
        for (SaleItem item : sale.getItems()) {
            SaleResponse.SaleItemResponse itemResponse = new SaleResponse.SaleItemResponse();
            itemResponse.setSaleItemId(item.getSaleItemId());
            itemResponse.setProdId(item.getProduct().getProdId());
            itemResponse.setProductName(item.getProduct().getProdName());
            itemResponse.setQuantity(item.getQuantity());
            itemResponse.setUnitPrice(item.getUnitPrice());
            itemResponses.add(itemResponse);
        }
        response.setItems(itemResponses);

        return response;
    }

    private String normalizeFilter(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    String normalizePaymentMethod(String paymentMethod) {
        if (paymentMethod == null) {
            return null;
        }

        String normalized = paymentMethod.trim();
        if (normalized.equalsIgnoreCase("Cash")) {
            return "Cash";
        }
        if (normalized.equalsIgnoreCase("Card")) {
            return "Card";
        }
        return normalized;
    }
}
