using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Globalization;

namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport;

public class GetDiscountSalesReportQueryHandler : IRequestHandler<GetDiscountSalesReportQuery, DiscountSalesReportDto>
{
    private readonly IApplicationDbContext _context;
    private static readonly PersianCalendar PersianCalendar = new();

    public GetDiscountSalesReportQueryHandler(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<DiscountSalesReportDto> Handle(GetDiscountSalesReportQuery request, CancellationToken cancellationToken)
    {
        var result = new DiscountSalesReportDto();

        // Base query for orders
        var ordersQuery = _context.DiscountOrders.AsQueryable();

        // Apply date filters
        if (request.FromDate.HasValue)
        {
            ordersQuery = ordersQuery.Where(o => o.Created >= request.FromDate.Value);
        }

        if (request.ToDate.HasValue)
        {
            ordersQuery = ordersQuery.Where(o => o.Created <= request.ToDate.Value);
        }

        // Get all matching orders for summary
        var orders = await ordersQuery.ToListAsync(cancellationToken);
        var completedOrders = orders.Where(o => o.PaymentStatus == PaymentStatus.Success).ToList();

        // Calculate summary
        result.Summary = new SalesSummary
        {
            TotalOrders = orders.Count,
            CompletedOrders = completedOrders.Count,
            PendingOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Pending),
            CancelledOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Reject),
            TotalSalesAmount = completedOrders.Sum(o => o.TotalAmount),
            TotalDiscountUsed = completedOrders.Sum(o => o.DiscountBalanceUsed),
            TotalGatewayPaid = completedOrders.Sum(o => o.GatewayAmountPaid),
            TotalVatAmount = completedOrders.Sum(o => o.VatAmount),
            AverageOrderValue = completedOrders.Any() ? (long)completedOrders.Average(o => o.TotalAmount) : 0,
            UniqueCustomers = orders.Select(o => o.UserId).Distinct().Count()
        };

        // Get total products sold
        var orderIds = completedOrders.Select(o => o.Id).ToList();
        result.Summary.TotalProductsSold = await _context.DiscountOrderDetails
            .Where(d => orderIds.Contains(d.DiscountOrderId))
            .SumAsync(d => d.Count, cancellationToken);

        // Generate period-based reports
        if (request.ReportType != SalesReportType.Summary && completedOrders.Any())
        {
            result.Periods = GeneratePeriodReport(completedOrders, request.ReportType);
        }

        // Get top selling products
        result.TopProducts = await GetTopSellingProducts(orderIds, cancellationToken);

        return result;
    }

    private List<SalesPeriodDto> GeneratePeriodReport(List<Domain.Entities.DiscountShop.DiscountOrder> orders, SalesReportType reportType)
    {
        var periods = new List<SalesPeriodDto>();

        var groupedOrders = reportType switch
        {
            SalesReportType.Daily => orders.GroupBy(o => o.Created.Date),
            SalesReportType.Weekly => orders.GroupBy(o => GetStartOfWeek(o.Created)),
            SalesReportType.Monthly => orders.GroupBy(o => new DateTime(o.Created.Year, o.Created.Month, 1)),
            _ => orders.GroupBy(o => o.Created.Date)
        };

        foreach (var group in groupedOrders.OrderBy(g => g.Key))
        {
            var periodStart = group.Key;
            var periodEnd = reportType switch
            {
                SalesReportType.Daily => periodStart.AddDays(1).AddSeconds(-1),
                SalesReportType.Weekly => periodStart.AddDays(7).AddSeconds(-1),
                SalesReportType.Monthly => periodStart.AddMonths(1).AddSeconds(-1),
                _ => periodStart.AddDays(1).AddSeconds(-1)
            };

            periods.Add(new SalesPeriodDto
            {
                PeriodLabel = GetPersianPeriodLabel(periodStart, reportType),
                PeriodStart = periodStart,
                PeriodEnd = periodEnd,
                OrdersCount = group.Count(),
                TotalAmount = group.Sum(o => o.TotalAmount),
                DiscountUsed = group.Sum(o => o.DiscountBalanceUsed),
                GatewayPaid = group.Sum(o => o.GatewayAmountPaid)
            });
        }

        return periods;
    }

    private async Task<List<TopSellingProductDto>> GetTopSellingProducts(List<long> orderIds, CancellationToken cancellationToken)
    {
        if (!orderIds.Any())
            return new List<TopSellingProductDto>();

        var topProducts = await _context.DiscountOrderDetails
            .Where(d => orderIds.Contains(d.DiscountOrderId))
            .GroupBy(d => d.ProductId)
            .Select(g => new
            {
                ProductId = g.Key,
                QuantitySold = g.Sum(d => d.Count),
                TotalRevenue = g.Sum(d => d.FinalPrice),
                OrdersCount = g.Select(d => d.DiscountOrderId).Distinct().Count()
            })
            .OrderByDescending(x => x.QuantitySold)
            .Take(10)
            .ToListAsync(cancellationToken);

        var productIds = topProducts.Select(p => p.ProductId).ToList();
        var products = await _context.DiscountProducts
            .Where(p => productIds.Contains(p.Id))
            .Select(p => new { p.Id, p.Title, p.ThumbnailPath })
            .ToListAsync(cancellationToken);

        return topProducts.Select(tp => new TopSellingProductDto
        {
            ProductId = tp.ProductId,
            ProductTitle = products.FirstOrDefault(p => p.Id == tp.ProductId)?.Title ?? "نامشخص",
            ImagePath = products.FirstOrDefault(p => p.Id == tp.ProductId)?.ThumbnailPath,
            QuantitySold = tp.QuantitySold,
            TotalRevenue = tp.TotalRevenue,
            OrdersCount = tp.OrdersCount
        }).ToList();
    }

    private static DateTime GetStartOfWeek(DateTime date)
    {
        // شروع هفته از شنبه (Saturday = 6 in DayOfWeek)
        var diff = ((int)date.DayOfWeek + 1) % 7; // Saturday = 0
        return date.AddDays(-diff).Date;
    }

    private static string GetPersianPeriodLabel(DateTime date, SalesReportType reportType)
    {
        var persianYear = PersianCalendar.GetYear(date);
        var persianMonth = PersianCalendar.GetMonth(date);
        var persianDay = PersianCalendar.GetDayOfMonth(date);

        return reportType switch
        {
            SalesReportType.Daily => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}",
            SalesReportType.Weekly => $"هفته {GetPersianWeekOfYear(date)} - {persianYear}",
            SalesReportType.Monthly => $"{GetPersianMonthName(persianMonth)} {persianYear}",
            _ => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}"
        };
    }

    private static int GetPersianWeekOfYear(DateTime date)
    {
        var firstDayOfYear = PersianCalendar.ToDateTime(PersianCalendar.GetYear(date), 1, 1, 0, 0, 0, 0);
        var daysSinceStart = (date - firstDayOfYear).Days;
        return (daysSinceStart / 7) + 1;
    }

    private static string GetPersianMonthName(int month) => month switch
    {
        1 => "فروردین",
        2 => "اردیبهشت",
        3 => "خرداد",
        4 => "تیر",
        5 => "مرداد",
        6 => "شهریور",
        7 => "مهر",
        8 => "آبان",
        9 => "آذر",
        10 => "دی",
        11 => "بهمن",
        12 => "اسفند",
        _ => "نامشخص"
    };
}
