using Meezi.API.Models.Expenses;
using Meezi.API.Services;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace Meezi.API.Tests;

public class ExpenseServiceTests
{
    private static (
        AppDbContext Db,
        ExpenseService Expenses,
        ShiftService Shifts,
        DailyReportService Reports,
        string CafeId,
        string BranchId,
        string UserId)
        CreateFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-exp";
        var branchId = "branch-exp";
        var userId = "mgr-1";

        db.Cafes.Add(new Cafe { Id = cafeId, Name = "Exp Cafe", Slug = "exp-cafe", PlanTier = PlanTier.Pro });
        db.Branches.Add(new Branch
        {
            Id = branchId,
            CafeId = cafeId,
            Name = "Main",
            IsActive = true,
            UpdatedAt = DateTime.UtcNow
        });
        db.Employees.Add(new Employee
        {
            Id = userId,
            CafeId = cafeId,
            BranchId = branchId,
            Name = "Manager",
            Phone = "09123333333",
            Role = EmployeeRole.Manager
        });
        db.SaveChanges();

        var shifts = new ShiftService(db);
        var expenses = new ExpenseService(db, shifts);
        var reports = new DailyReportService(db, Microsoft.Extensions.Logging.Abstractions.NullLogger<DailyReportService>.Instance);
        return (db, expenses, shifts, reports, cafeId, branchId, userId);
    }

    [Fact]
    public async Task CreateExpense_WithOpenShift_RecordsWithdrawal_AndReducesExpectedCash()
    {
        var (_, expenses, shifts, _, cafeId, branchId, userId) = CreateFixture();

        var opened = await shifts.OpenShiftAsync(cafeId, branchId, 1_000_000m, userId);
        var shiftId = opened.Data!.Id;

        var created = await expenses.CreateExpenseAsync(
            cafeId,
            new CreateExpenseRequest(branchId, shiftId, ExpenseCategory.Supplies, 150_000m, "خرید شیر", null),
            userId);

        Assert.True(created.Success);
        Assert.Equal(150_000m, created.Data!.Amount);

        var shift = await shifts.CloseShiftAsync(cafeId, shiftId, 850_000m, userId);
        Assert.True(shift.Success);
        Assert.Equal(850_000m, shift.Data!.ExpectedCash);
    }

    [Fact]
    public async Task DeleteExpense_SoftDeletesExpenseAndWithdrawal()
    {
        var (db, expenses, shifts, _, cafeId, branchId, userId) = CreateFixture();

        var opened = await shifts.OpenShiftAsync(cafeId, branchId, 500_000m, userId);
        var shiftId = opened.Data!.Id;

        var created = await expenses.CreateExpenseAsync(
            cafeId,
            new CreateExpenseRequest(branchId, shiftId, ExpenseCategory.Other, 50_000m, null, null),
            userId);
        Assert.True(created.Success);

        var deleted = await expenses.DeleteExpenseAsync(cafeId, created.Data!.Id);
        Assert.True(deleted.Success);

        var expenseRow = await db.Expenses.IgnoreQueryFilters()
            .FirstAsync(e => e.Id == created.Data.Id);
        Assert.NotNull(expenseRow.DeletedAt);

        var tx = await db.CashTransactions.IgnoreQueryFilters()
            .FirstAsync(t => t.ReferenceId == created.Data.Id);
        Assert.NotNull(tx.DeletedAt);
    }

    [Fact]
    public async Task DailyReport_Expense_ReducesNetIncome_NotRevenue()
    {
        var (db, expenses, shifts, reports, cafeId, branchId, userId) = CreateFixture();
        var date = IranCalendar.TodayInIran;
        var (start, _) = IranCalendar.GetUtcRangeForIranDay(date);

        var catId = "cat-1";
        var itemId = "item-1";
        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 = "Tea",
            NameEn = "Tea",
            Price = 100_000m,
            IsAvailable = true
        });

        var orderId = "ord-1";
        db.Orders.Add(new Order
        {
            Id = orderId,
            CafeId = cafeId,
            BranchId = branchId,
            Status = OrderStatus.Delivered,
            OrderType = OrderType.DineIn,
            Subtotal = 100_000m,
            TaxTotal = 0,
            Total = 100_000m,
            CreatedAt = start.AddHours(10)
        });
        db.OrderItems.Add(new OrderItem
        {
            OrderId = orderId,
            MenuItemId = itemId,
            Quantity = 1,
            UnitPrice = 100_000m
        });
        await db.SaveChangesAsync();

        await expenses.CreateExpenseAsync(
            cafeId,
            new CreateExpenseRequest(branchId, null, ExpenseCategory.Utilities, 30_000m, "برق", null),
            userId);

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

        Assert.Equal(100_000m, report.TotalRevenue);
        Assert.Equal(30_000m, report.TotalExpenses);
        Assert.Equal(70_000m, report.NetIncome);
    }
}
