using Meezi.API.Services;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace Meezi.API.Tests;

public class DailyReportServiceTests
{
    private static (AppDbContext Db, DailyReportService Service, string CafeId, string BranchId, string ItemId)
        CreateFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-report";
        var branchId = "branch-1";
        var branch2 = "branch-2";
        var itemId = "item-espresso";
        var catId = "cat-1";
        var userId = "emp-1";

        db.Cafes.Add(new Cafe { Id = cafeId, Name = "R", Slug = "r", PlanTier = PlanTier.Business });
        db.Branches.Add(new Branch { Id = branchId, CafeId = cafeId, Name = "A", IsActive = true, UpdatedAt = DateTime.UtcNow });
        db.Branches.Add(new Branch { Id = branch2, CafeId = cafeId, Name = "B", IsActive = true, UpdatedAt = DateTime.UtcNow });
        db.Employees.Add(new Employee { Id = userId, CafeId = cafeId, BranchId = branchId, Name = "E", Phone = "09120000001", Role = EmployeeRole.Owner });
        db.MenuCategories.Add(new MenuCategory { Id = catId, CafeId = cafeId, Name = "C", NameEn = "C", SortOrder = 0 });
        db.MenuItems.Add(new MenuItem { Id = itemId, CafeId = cafeId, CategoryId = catId, Name = "Espresso", NameEn = "Espresso", Price = 50_000m, IsAvailable = true });
        db.SaveChanges();

        var service = new DailyReportService(db, NullLogger<DailyReportService>.Instance);
        return (db, service, cafeId, branchId, itemId);
    }

    private static async Task SeedClosedOrderAsync(
        AppDbContext db,
        string cafeId,
        string branchId,
        string itemId,
        DateTime createdAt,
        bool voidFirstLine,
        IReadOnlyList<(PaymentMethod Method, decimal Amount)>? payments = null,
        int activeQty = 1)
    {
        var orderId = $"ord_{Guid.NewGuid():N}"[..16];
        var activeRevenue = 50_000m * activeQty;
        var order = new Order
        {
            Id = orderId,
            CafeId = cafeId,
            BranchId = branchId,
            Status = OrderStatus.Delivered,
            OrderType = OrderType.DineIn,
            Subtotal = activeRevenue,
            TaxTotal = 0,
            Total = activeRevenue,
            CreatedAt = createdAt
        };
        db.Orders.Add(order);
        if (voidFirstLine)
        {
            db.OrderItems.Add(new OrderItem
            {
                OrderId = orderId,
                MenuItemId = itemId,
                Quantity = 2,
                UnitPrice = 50_000m,
                IsVoided = true,
                VoidedAt = createdAt
            });
        }

        db.OrderItems.Add(new OrderItem
        {
            OrderId = orderId,
            MenuItemId = itemId,
            Quantity = activeQty,
            UnitPrice = 50_000m
        });

        if (payments is not null)
        {
            var shiftId = $"shift_{Guid.NewGuid():N}"[..16];
            db.RegisterShifts.Add(new Shift
            {
                Id = shiftId,
                CafeId = cafeId,
                BranchId = branchId,
                OpenedByUserId = "emp-1",
                OpenedAt = createdAt.AddHours(-1),
                OpeningCash = 0,
                Status = ShiftStatus.Open,
                CreatedAt = createdAt.AddHours(-1)
            });
            foreach (var p in payments)
            {
                db.CashTransactions.Add(new CashTransaction
                {
                    CafeId = cafeId,
                    BranchId = branchId,
                    ShiftId = shiftId,
                    Type = CashTransactionType.OrderPayment,
                    Method = p.Method,
                    Amount = p.Amount,
                    ReferenceId = orderId,
                    CreatedByUserId = "emp-1",
                    CreatedAt = createdAt.AddMinutes(5)
                });
            }
        }

        await db.SaveChangesAsync();
    }

    [Fact]
    public async Task GenerateReport_IsIdempotent()
    {
        var (db, service, cafeId, branchId, itemId) = CreateFixture();
        var date = IranCalendar.TodayInIran.AddDays(-1);
        var (start, _) = IranCalendar.GetUtcRangeForIranDay(date);
        await SeedClosedOrderAsync(db, cafeId, branchId, itemId, start.AddHours(12), voidFirstLine: false);

        var first = await service.GenerateReportAsync(cafeId, branchId, date);
        var second = await service.GenerateReportAsync(cafeId, branchId, date);

        Assert.Equal(first.Id, second.Id);
        Assert.Equal(first.TotalRevenue, second.TotalRevenue);
        Assert.Equal(1, await db.DailyReports.CountAsync(r => r.CafeId == cafeId && r.BranchId == branchId && r.Date == date));
    }

    [Fact]
    public async Task GenerateReport_ExcludesVoidedItemsFromRevenue()
    {
        var (db, service, cafeId, branchId, itemId) = CreateFixture();
        var date = IranCalendar.TodayInIran;
        var (start, _) = IranCalendar.GetUtcRangeForIranDay(date);
        await SeedClosedOrderAsync(db, cafeId, branchId, itemId, start.AddHours(10), voidFirstLine: true);

        var report = await service.GenerateReportAsync(cafeId, branchId, date);

        Assert.Equal(50_000m, report.TotalRevenue);
        Assert.Equal(1, report.TotalVoids);
        Assert.Equal(100_000m, report.VoidAmount);
    }

    [Fact]
    public async Task GenerateReport_SumsPaymentMethods_FromCashTransactions()
    {
        var (db, service, cafeId, branchId, itemId) = CreateFixture();
        var date = IranCalendar.TodayInIran;
        var (start, _) = IranCalendar.GetUtcRangeForIranDay(date);
        await SeedClosedOrderAsync(
            db, cafeId, branchId, itemId, start.AddHours(11), false,
            payments:
            [
                (PaymentMethod.Cash, 60_000m),
                (PaymentMethod.Card, 40_000m),
                (PaymentMethod.Credit, 50_000m)
            ]);

        var report = await service.GenerateReportAsync(cafeId, branchId, date);

        Assert.Equal(60_000m, report.CashRevenue);
        Assert.Equal(40_000m, report.CardRevenue);
        Assert.Equal(50_000m, report.CreditRevenue);
    }

    [Fact]
    public async Task GetSummary_AggregatesAcrossBranches()
    {
        var (db, service, cafeId, branchId, itemId) = CreateFixture();
        var branch2 = "branch-2";
        var date = IranCalendar.TodayInIran;
        var (start, _) = IranCalendar.GetUtcRangeForIranDay(date);

        await SeedClosedOrderAsync(db, cafeId, branchId, itemId, start.AddHours(9), false);
        await SeedClosedOrderAsync(db, cafeId, branch2, itemId, start.AddHours(10), false);

        await service.GenerateReportAsync(cafeId, branchId, date);
        await service.GenerateReportAsync(cafeId, branch2, date);

        var summary = await service.GetSummaryAsync(cafeId, 30);

        Assert.Equal(2, summary.ByBranch.Count);
        Assert.Equal(100_000m, summary.TotalRevenue);
        Assert.Equal(100_000m, summary.ByBranch.Sum(b => b.TotalRevenue));
        Assert.Equal(2, summary.TotalOrders);
    }

    [Fact]
    public void ReportPlanGate_Free_AllowsEightDayWindow()
    {
        var today = new DateOnly(2026, 5, 21);
        Assert.True(ReportPlanGate.IsDateInRange(PlanTier.Free, today, today));
        Assert.True(ReportPlanGate.IsDateInRange(PlanTier.Free, today.AddDays(-7), today));
        Assert.False(ReportPlanGate.IsDateInRange(PlanTier.Free, today.AddDays(-8), today));
    }
}
