package com.petshop.backend.service;

import com.petshop.backend.dto.analytics.DashboardResponse;
import com.petshop.backend.entity.Inventory;
import com.petshop.backend.entity.Product;
import com.petshop.backend.entity.Sale;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.InventoryRepository;
import com.petshop.backend.repository.ProductRepository;
import com.petshop.backend.repository.SaleRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AnalyticsService {

    private final SaleRepository saleRepository;
    private final InventoryRepository inventoryRepository;
    private final ProductRepository productRepository;

    public AnalyticsService(SaleRepository saleRepository,
                          InventoryRepository inventoryRepository, ProductRepository productRepository) {
        this.saleRepository = saleRepository;
        this.inventoryRepository = inventoryRepository;
        this.productRepository = productRepository;
    }

    @Transactional(readOnly = true)
    public DashboardResponse getDashboardData(int days, int top, User user) {
        return getDashboardData(days, top, user, null, null, null, null);
    }

    @Transactional(readOnly = true)
    public DashboardResponse getDashboardData(int days, int top, User user,
                                               String paymentMethod, Long storeId,
                                               String channel, LocalDateTime endDate) {
        LocalDateTime startDate = LocalDateTime.now().minusDays(days);

        List<Sale> sales = saleRepository.findForAnalytics(startDate, endDate, paymentMethod, storeId, channel)
                .stream()
                .filter(sale -> includeSaleForUser(sale, user))
                .collect(Collectors.toList());

        DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales);
        DashboardResponse.InventorySummary inventorySummary = user.getRole() == User.Role.ADMIN ? calculateInventorySummary() : null;
        List<DashboardResponse.TopProduct> topProducts = calculateTopProducts(sales, top);
        List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
        List<DashboardResponse.PaymentMethodData> paymentMethods = calculatePaymentMethods(sales);
        List<DashboardResponse.EmployeePerformanceData> employeePerformance = calculateEmployeePerformance(sales, user);

        return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance);
    }

    private DashboardResponse.SalesSummary calculateSalesSummary(List<Sale> sales) {
        BigDecimal totalRevenue = sales.stream()
                .filter(sale -> !sale.getIsRefund())
                .map(Sale::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        Long totalSales = sales.stream()
                .filter(sale -> !sale.getIsRefund())
                .count();

        BigDecimal totalRefunds = sales.stream()
                .filter(Sale::getIsRefund)
                .map(Sale::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        Long totalRefundCount = sales.stream()
                .filter(Sale::getIsRefund)
                .count();

        Long totalItemsSold = sales.stream()
                .filter(sale -> !sale.getIsRefund())
                .flatMap(sale -> sale.getItems().stream())
                .mapToLong(item -> item.getQuantity())
                .sum();

        return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold);
    }

    private DashboardResponse.InventorySummary calculateInventorySummary() {
        List<Inventory> allInventory = inventoryRepository.findAll();

        Long totalProducts = productRepository.count();

        Long lowStockProducts = allInventory.stream()
                .filter(inv -> inv.getQuantity() > 0 && inv.getQuantity() <= 10)
                .map(inv -> inv.getProduct().getProdId())
                .distinct()
                .count();

        Long outOfStockProducts = allInventory.stream()
                .filter(inv -> inv.getQuantity() == 0)
                .map(inv -> inv.getProduct().getProdId())
                .distinct()
                .count();

        return new DashboardResponse.InventorySummary(totalProducts, lowStockProducts, outOfStockProducts);
    }

    private List<DashboardResponse.TopProduct> calculateTopProducts(List<Sale> sales, int top) {
        Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();

        for (Sale sale : sales) {
            if (sale.getIsRefund()) {
                continue;
            }
            for (var item : sale.getItems()) {
                Long productId = item.getProduct().getProdId();
                String productName = item.getProduct().getProdName();
                Long quantitySold = Long.valueOf(item.getQuantity());
                BigDecimal revenue = item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity()));

                productSalesMap.compute(productId, (key, existing) -> {
                    if (existing == null) {
                        return new DashboardResponse.TopProduct(productId, productName, quantitySold, revenue);
                    } else {
                        existing.setQuantitySold(existing.getQuantitySold() + quantitySold);
                        existing.setRevenue(existing.getRevenue().add(revenue));
                        return existing;
                    }
                });
            }
        }

        return productSalesMap.values().stream()
                .sorted((p1, p2) -> p2.getRevenue().compareTo(p1.getRevenue()))
                .limit(top)
                .collect(Collectors.toList());
    }

    private List<DashboardResponse.DailySales> calculateDailySales(List<Sale> sales, int days) {
        Map<LocalDate, DashboardResponse.DailySales> dailySalesMap = new LinkedHashMap<>();

        LocalDate startDate = LocalDate.now().minusDays(days - 1);
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            String dateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
            dailySalesMap.put(date, new DashboardResponse.DailySales(dateStr, BigDecimal.ZERO, 0L));
        }

        for (Sale sale : sales) {
            if (sale.getIsRefund()) {
                continue;
            }
            LocalDate saleDate = sale.getSaleDate().toLocalDate();
            if (dailySalesMap.containsKey(saleDate)) {
                DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
                dailySale.setRevenue(dailySale.getRevenue().add(sale.getTotalAmount()));
                dailySale.setSalesCount(dailySale.getSalesCount() + 1);
            }
        }

        return new ArrayList<>(dailySalesMap.values());
    }

    private List<DashboardResponse.PaymentMethodData> calculatePaymentMethods(List<Sale> sales) {
        return sales.stream()
                .filter(sale -> !sale.getIsRefund())
                .collect(Collectors.groupingBy(
                        sale -> sale.getPaymentMethod() == null ? "Unknown" : sale.getPaymentMethod(),
                        TreeMap::new,
                        Collectors.counting()))
                .entrySet().stream()
                .map(entry -> new DashboardResponse.PaymentMethodData(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    private List<DashboardResponse.EmployeePerformanceData> calculateEmployeePerformance(List<Sale> sales, User user) {
        Map<String, BigDecimal> employeeRevenue = new TreeMap<>();

        for (Sale sale : sales) {
            if (sale.getIsRefund()) {
                continue;
            }
            String employeeName = sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName();
            employeeRevenue.merge(employeeName, sale.getTotalAmount(), BigDecimal::add);
        }

        if (user.getRole() == User.Role.STAFF && employeeRevenue.isEmpty()) {
            String employeeName = user.getFirstName() + " " + user.getLastName();
            employeeRevenue.put(employeeName, BigDecimal.ZERO);
        }

        return employeeRevenue.entrySet().stream()
                .map(entry -> new DashboardResponse.EmployeePerformanceData(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    private boolean includeSaleForUser(Sale sale, User user) {
        if (user.getRole() == User.Role.ADMIN) {
            return true;
        }
        if (user.getRole() == User.Role.STAFF) {
            return sale.getEmployee() != null && sale.getEmployee().getId() != null && sale.getEmployee().getId().equals(user.getId());
        }
        return false;
    }
}
