package com.pressing.modules.statistics;

import com.pressing.domain.catalog.ServiceOfferingRepository;
import com.pressing.domain.customer.Customer;
import com.pressing.domain.customer.CustomerRepository;
import com.pressing.domain.invoice.*;
import com.pressing.domain.payment.Payment;
import com.pressing.domain.payment.PaymentRepository;
import com.pressing.domain.pickup.PickupLine;
import com.pressing.domain.pickup.PickupLineRepository;
import com.pressing.modules.common.DomainSupport;
import com.pressing.modules.statistics.dto.StatisticsDtos.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class StatisticsService {

    private final InvoiceRepository invoiceRepository;
    private final CustomerRepository customerRepository;
    private final PickupLineRepository pickupLineRepository;
    private final ServiceOfferingRepository serviceOfferingRepository;
    private final PaymentRepository paymentRepository;
    private final DomainSupport domainSupport;

    public StatisticsService(
            InvoiceRepository invoiceRepository,
            CustomerRepository customerRepository,
            PickupLineRepository pickupLineRepository,
            ServiceOfferingRepository serviceOfferingRepository,
            PaymentRepository paymentRepository,
            DomainSupport domainSupport
    ) {
        this.invoiceRepository = invoiceRepository;
        this.customerRepository = customerRepository;
        this.pickupLineRepository = pickupLineRepository;
        this.serviceOfferingRepository = serviceOfferingRepository;
        this.paymentRepository = paymentRepository;
        this.domainSupport = domainSupport;
    }

    // --- 1. ÉTAT RÉCAPITULATIF DES ACTIVITÉS ---
    public SummaryReportResponse getSummaryReport(int year, int quarter) {
        Long siteId = domainSupport.siteId();

        int startMonth = (quarter - 1) * 3 + 1;
        LocalDate startDate = LocalDate.of(year, startMonth, 1);
        LocalDate endDate = startDate.plusMonths(3).minusDays(1);

        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();

        // Customer#createdAt (hérité de BaseEntity) est un Instant : conversion nécessaire
        // avant l'appel au repository, sinon Hibernate rejette le filtre (plantage 500).
        Instant startInstant = startDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Instant endInstant = endDateTime.atZone(ZoneId.systemDefault()).toInstant();
        List<Customer> newCustomers = customerRepository.findBySiteIdAndCreatedAtBetween(siteId, startInstant, endInstant);
        List<Invoice> quarterInvoices = invoiceRepository.findBySiteIdAndDepositDateTimeBetweenAndStatusNot(siteId, startDateTime, endDateTime, InvoiceStatus.CANCELLED);

        List<MonthlyStat> newCustomersByMonth = groupCustomersByMonth(newCustomers, startMonth);

        List<TopCustomer> top10Customers = quarterInvoices.stream()
                .collect(Collectors.groupingBy(Invoice::getCustomer))
                .entrySet().stream()
                .map(e -> new TopCustomer(
                        e.getKey().getId(),
                        e.getKey().getFirstName() + " " + e.getKey().getLastName(),
                        e.getKey().getPhone(),
                        e.getValue().size(),
                        e.getValue().stream().map(Invoice::getAmountTtc).reduce(BigDecimal.ZERO, BigDecimal::add)
                ))
                .sorted(Comparator.comparing(TopCustomer::totalSpent).reversed())
                .limit(10)
                .toList();

        List<MonthlyStat> depositsByMonth = groupInvoicesByMonth(quarterInvoices, startMonth);
        List<MonthlyArticlesStat> articlesByMonth = calculateArticlesByMonth(siteId, startDateTime, endDateTime, startMonth);

        List<ServiceDistribution> depositsByService = calculateServiceDistribution(quarterInvoices);
        List<ServiceDistribution> pickupsByService = pickupLineRepository.findPickupDistributionByService(siteId, startDateTime, endDateTime);
        List<CategoryDistribution> depositsByCategory = calculateCategoryDistribution(quarterInvoices);

        List<TopArticle> top10Articles = quarterInvoices.stream()
                .flatMap(i -> i.getLines().stream())
                .collect(Collectors.groupingBy(InvoiceLine::getDesignation, Collectors.summingLong(InvoiceLine::getQuantity)))
                .entrySet().stream()
                .map(e -> new TopArticle(e.getKey(), e.getValue()))
                .sorted(Comparator.comparing(TopArticle::quantity).reversed())
                .limit(10)
                .toList();

        long readyNotPickedUp = quarterInvoices.stream()
                .filter(i -> i.getStatus() == InvoiceStatus.READY)
                .mapToLong(Invoice::getArticleCount)
                .sum();

        List<MonthlyRevenue> revenueByMonth = calculateRevenueByMonth(quarterInvoices, startMonth);

        // Encaissements réels du trimestre : tous les règlements (lavage + repassage) perçus
        // dans la période, basés sur Payment.paymentDateTime (recouvrement unifié).
        List<Payment> quarterPayments = paymentRepository.findBySiteIdAndPaymentDateTimeBetween(siteId, startDateTime, endDateTime);
        List<MonthlyCollectionStat> collectionsByMonth = calculateCollectionsByMonth(quarterPayments, startMonth);

        return new SummaryReportResponse(
                year, quarter, newCustomersByMonth, top10Customers, depositsByMonth,
                articlesByMonth, depositsByService, pickupsByService, depositsByCategory,
                top10Articles, readyNotPickedUp, revenueByMonth, collectionsByMonth
        );
    }

    // --- 2. BILAN TRIMESTRIEL (MATRICE CLIENTS x SERVICES) ---
    public QuarterlyMatrixResponse getQuarterlyMatrix(int year, int quarter) {
        Long siteId = domainSupport.siteId();
        int startMonth = (quarter - 1) * 3 + 1;

        List<MonthlyMatrixResponse> monthResponses = new ArrayList<>();

        List<ServiceColumnDto> serviceColumns = serviceOfferingRepository.findBySiteIdOrderByLabelAsc(siteId)
                .stream()
                .map(s -> new ServiceColumnDto(s.getId(), s.getLabel()))
                .toList();

        for (int i = 0; i < 3; i++) {
            int currentMonthValue = startMonth + i;
            Month monthEnum = Month.of(currentMonthValue);
            LocalDate startOfMonth = LocalDate.of(year, currentMonthValue, 1);
            LocalDate endOfMonth = startOfMonth.plusMonths(1).minusDays(1);

            LocalDateTime startDateTime = startOfMonth.atStartOfDay();
            LocalDateTime endDateTime = endOfMonth.plusDays(1).atStartOfDay();

            List<Invoice> monthInvoices = invoiceRepository.findBySiteIdAndDepositDateTimeBetweenAndStatusNot(siteId, startDateTime, endDateTime, InvoiceStatus.CANCELLED);
            List<PickupLine> pickupLines = pickupLineRepository.findLinesForMatrix(siteId, startDateTime, endDateTime);

            Map<Long, Customer> customerMap = new HashMap<>();
            Map<Long, Map<Long, int[]>> matrixMap = new HashMap<>();

            // Calcul des Dépôts
            for (Invoice inv : monthInvoices) {
                Customer c = inv.getCustomer();
                customerMap.put(c.getId(), c);
                Long serviceId = inv.getService().getId();
                
                int totalQuantityInInvoice = inv.getLines().stream().mapToInt(InvoiceLine::getQuantity).sum();
                
                matrixMap.computeIfAbsent(c.getId(), k -> new HashMap<>())
                         .computeIfAbsent(serviceId, k -> new int[2])[0] += totalQuantityInInvoice;
            }

            // Calcul des Retraits
            for (PickupLine line : pickupLines) {
                Customer c = line.getPickup().getInvoice().getCustomer();
                customerMap.put(c.getId(), c);
                Long serviceId = line.getPickup().getInvoice().getService().getId();
                
                matrixMap.computeIfAbsent(c.getId(), k -> new HashMap<>())
                         .computeIfAbsent(serviceId, k -> new int[2])[1] += line.getQuantity();
            }

            List<ClientMatrixRowDto> rows = new ArrayList<>();

            for (Map.Entry<Long, Customer> entry : customerMap.entrySet()) {
                Long customerId = entry.getKey();
                Customer customer = entry.getValue();
                Map<Long, int[]> serviceMap = matrixMap.getOrDefault(customerId, Map.of());

                Map<Long, ClientServiceStatDto> serviceStatsDto = new HashMap<>();
                int totalDep = 0;
                int totalPick = 0;

                for (ServiceColumnDto sCol : serviceColumns) {
                    int[] stats = serviceMap.getOrDefault(sCol.serviceId(), new int[]{0, 0});
                    int dep = stats[0];
                    int pick = stats[1];

                    serviceStatsDto.put(sCol.serviceId(), new ClientServiceStatDto(dep, pick));
                    totalDep += dep;
                    totalPick += pick;
                }

                rows.add(new ClientMatrixRowDto(
                        customer.getId(),
                        customer.getCustomerNumber(),
                        customer.getFirstName() + " " + customer.getLastName(),
                        serviceStatsDto,
                        totalDep,
                        totalPick,
                        (totalDep - totalPick)
                ));
            }

            monthResponses.add(new MonthlyMatrixResponse(
                    i + 1,
                    monthEnum.getDisplayName(TextStyle.FULL, Locale.FRENCH).toUpperCase(),
                    serviceColumns,
                    rows
            ));
        }

        return new QuarterlyMatrixResponse(year, quarter, monthResponses);
    }

    // --- METHODES UTILITAIRES DE CALCUL ---
    private List<MonthlyStat> groupCustomersByMonth(List<Customer> list, int startMonth) {
        List<MonthlyStat> stats = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Month m = Month.of(startMonth + i);
            long count = list.stream().filter(c -> c.getCreatedAt().atZone(ZoneId.systemDefault()).getMonth() == m).count();
            stats.add(new MonthlyStat(m.getDisplayName(TextStyle.FULL, Locale.FRENCH), count));
        }
        return stats;
    }

    private List<MonthlyStat> groupInvoicesByMonth(List<Invoice> list, int startMonth) {
        List<MonthlyStat> stats = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Month m = Month.of(startMonth + i);
            long count = list.stream().filter(inv -> inv.getDepositDateTime().atZone(ZoneId.systemDefault()).getMonth() == m).count();
            stats.add(new MonthlyStat(m.getDisplayName(TextStyle.FULL, Locale.FRENCH), count));
        }
        return stats;
    }

    private List<MonthlyArticlesStat> calculateArticlesByMonth(Long siteId, LocalDateTime start, LocalDateTime end, int startMonth) {
        List<MonthlyArticlesStat> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Month m = Month.of(startMonth + i);
            long dep = invoiceRepository.findBySiteIdAndDepositDateTimeBetweenAndStatusNot(siteId, start, end, InvoiceStatus.CANCELLED)
                    .stream().filter(inv -> inv.getDepositDateTime().atZone(ZoneId.systemDefault()).getMonth() == m)
                    .mapToLong(Invoice::getArticleCount).sum();

            long pick = pickupLineRepository.countPickedUpArticlesByMonth(siteId, m.getValue());
            list.add(new MonthlyArticlesStat(m.getDisplayName(TextStyle.FULL, Locale.FRENCH), dep, pick));
        }
        return list;
    }

    private List<ServiceDistribution> calculateServiceDistribution(List<Invoice> invoices) {
        Map<String, Long> map = invoices.stream()
                .collect(Collectors.groupingBy(
                        i -> i.getService().getLabel(),
                        Collectors.summingLong(Invoice::getArticleCount)
                ));
        return map.entrySet().stream().map(e -> new ServiceDistribution(e.getKey(), e.getValue())).toList();
    }

    private List<CategoryDistribution> calculateCategoryDistribution(List<Invoice> invoices) {
        Map<String, Long> map = invoices.stream()
                .flatMap(i -> i.getLines().stream())
                .collect(Collectors.groupingBy(
                        line -> (line.getArticleType() != null) ? line.getArticleType().getLabel() : "Général",
                        Collectors.summingLong(InvoiceLine::getQuantity)
                ));
        return map.entrySet().stream().map(e -> new CategoryDistribution(e.getKey(), e.getValue())).toList();
    }

    private List<MonthlyCollectionStat> calculateCollectionsByMonth(List<Payment> payments, int startMonth) {
        List<MonthlyCollectionStat> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Month m = Month.of(startMonth + i);
            BigDecimal total = payments.stream()
                    .filter(p -> p.getPaymentDateTime().atZone(ZoneId.systemDefault()).getMonth() == m)
                    .map(Payment::getAmount)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            list.add(new MonthlyCollectionStat(m.getDisplayName(TextStyle.FULL, Locale.FRENCH), total));
        }
        return list;
    }

    private List<MonthlyRevenue> calculateRevenueByMonth(List<Invoice> invoices, int startMonth) {
        List<MonthlyRevenue> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Month m = Month.of(startMonth + i);
            List<Invoice> monthInvoices = invoices.stream().filter(inv -> inv.getDepositDateTime().atZone(ZoneId.systemDefault()).getMonth() == m).toList();
            BigDecimal ttc = monthInvoices.stream().map(Invoice::getAmountTtc).reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal paid = monthInvoices.stream().map(Invoice::getAmountPaid).reduce(BigDecimal.ZERO, BigDecimal::add);
            list.add(new MonthlyRevenue(m.getDisplayName(TextStyle.FULL, Locale.FRENCH), ttc, paid));
        }
        return list;
    }
}