using Meezi.API.Models.Menu;
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 BranchMenuTests
{
    private static (AppDbContext Db, BranchMenuService Menu, string CafeId, string BranchId, string ItemA, string ItemB)
        CreateFixture(PlanTier tier = PlanTier.Pro)
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-1";
        var branchId = "branch-1";
        var catId = "cat-1";
        var itemA = "item-a";
        var itemB = "item-b";

        db.Cafes.Add(new Cafe { Id = cafeId, Name = "Test", Slug = "test", PlanTier = tier });
        db.Branches.Add(new Branch
        {
            Id = branchId,
            CafeId = cafeId,
            Name = "Main",
            IsActive = true,
            UpdatedAt = DateTime.UtcNow
        });
        db.MenuCategories.Add(new MenuCategory
        {
            Id = catId,
            CafeId = cafeId,
            Name = "Drinks",
            NameEn = "Drinks",
            SortOrder = 0,
            IsActive = true
        });
        db.MenuItems.AddRange(
            new MenuItem
            {
                Id = itemA,
                CafeId = cafeId,
                CategoryId = catId,
                Name = "Espresso",
                Price = 100_000m,
                IsAvailable = true
            },
            new MenuItem
            {
                Id = itemB,
                CafeId = cafeId,
                CategoryId = catId,
                Name = "Latte",
                Price = 150_000m,
                IsAvailable = true
            });
        db.SaveChanges();

        return (db, new BranchMenuService(db), cafeId, branchId, itemA, itemB);
    }

    [Fact]
    public async Task GetBranchMenu_ExcludesUnavailableItems()
    {
        var (_, menu, cafeId, branchId, itemA, itemB) = CreateFixture();
        await menu.UpsertOverrideAsync(
            cafeId, branchId, itemB,
            new UpsertBranchMenuOverrideRequest(false, null),
            PlanTier.Pro, EmployeeRole.Manager, "user-1");

        var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false);

        Assert.NotNull(result);
        Assert.Single(result!);
        Assert.Equal(itemA, result![0].Id);
    }

    [Fact]
    public async Task GetBranchMenu_AppliesPriceOverride_WhenSet()
    {
        var (_, menu, cafeId, branchId, itemA, _) = CreateFixture();
        await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 88_000m),
            PlanTier.Pro, EmployeeRole.Manager, "user-1");

        var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false);

        Assert.NotNull(result);
        var row = Assert.Single(result!, r => r.Id == itemA);
        Assert.Equal(100_000m, row.MasterPrice);
        Assert.Equal(88_000m, row.EffectivePrice);
        Assert.True(row.HasPriceOverride);
    }

    [Fact]
    public async Task GetBranchMenu_UsesMasterPrice_WhenNoOverride()
    {
        var (_, menu, cafeId, branchId, itemA, _) = CreateFixture();

        var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false);

        var row = Assert.Single(result!, r => r.Id == itemA);
        Assert.Equal(100_000m, row.MasterPrice);
        Assert.Equal(100_000m, row.EffectivePrice);
        Assert.False(row.IsOverridden);
    }

    [Fact]
    public async Task UpsertOverride_FreePlan_PriceOverride_ReturnsPlanLimitReached()
    {
        var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(PlanTier.Free);

        var result = await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 90_000m),
            PlanTier.Free,
            EmployeeRole.Manager,
            "user-1");

        Assert.False(result.Success);
        Assert.Equal("PLAN_LIMIT_REACHED", result.ErrorCode);
    }

    [Fact]
    public async Task UpsertOverride_ProPlan_PriceOverride_Succeeds()
    {
        var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(PlanTier.Pro);

        var result = await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 90_000m),
            PlanTier.Pro,
            EmployeeRole.Manager,
            "user-1");

        Assert.True(result.Success);
        Assert.Equal(90_000m, result.Data!.PriceOverride);
    }

    [Fact]
    public async Task DeleteOverride_ResetsToMasterPrice()
    {
        var (_, menu, cafeId, branchId, itemA, _) = CreateFixture();
        await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 90_000m),
            PlanTier.Pro, EmployeeRole.Owner, "owner-1");

        var deleted = await menu.DeleteOverrideAsync(cafeId, branchId, itemA);
        Assert.True(deleted);

        var menuRows = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false);
        var row = Assert.Single(menuRows!, r => r.Id == itemA);
        Assert.Equal(100_000m, row.EffectivePrice);
        Assert.False(row.IsOverridden);
    }

    [Fact]
    public async Task Override_UniqueConstraint_UpsertNotDuplicate()
    {
        var (db, menu, cafeId, branchId, itemA, _) = CreateFixture();
        await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 80_000m),
            PlanTier.Pro, EmployeeRole.Manager, "user-1");
        await menu.UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(false, 85_000m),
            PlanTier.Pro, EmployeeRole.Manager, "user-1");

        var count = await db.BranchMenuItemOverrides
            .CountAsync(o => o.BranchId == branchId && o.MenuItemId == itemA);
        Assert.Equal(1, count);

        var rows = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: true);
        Assert.Equal(2, rows!.Count);
        var hidden = Assert.Single(rows, r => r.Id == itemA);
        Assert.False(hidden.IsAvailable);
        Assert.Equal(85_000m, hidden.EffectivePrice);
    }
}
