using Meezi.API.Models.Menu;
using Meezi.API.Models.Orders;
using Meezi.API.Models.Public;
using Meezi.API.Services;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Xunit;
using LiveOrderDto = Meezi.API.Models.Orders.LiveOrderDto;

namespace Meezi.API.Tests;

public class QrMenuTests
{
    private sealed class NoOpKds : IKdsNotifier
    {
        public Task NotifyOrderCreatedAsync(string cafeId, LiveOrderDto order, CancellationToken ct = default) =>
            Task.CompletedTask;
        public Task NotifyOrderStatusChangedAsync(string cafeId, string orderId, OrderStatus status, CancellationToken ct = default) =>
            Task.CompletedTask;
        public Task NotifyTableStatusChangedAsync(string cafeId, CancellationToken ct = default) =>
            Task.CompletedTask;
    }

    private static (
        AppDbContext Db,
        TableService Tables,
        PublicService Public,
        string CafeId,
        string BranchId,
        string TableId,
        string ItemA,
        string ItemB,
        string QrCode
    ) CreateFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-qr";
        var branchId = "branch-qr";
        var catId = "cat-qr";
        var itemA = "item-qr-a";
        var itemB = "item-qr-b";
        var tableId = "table-qr-1";
        const string qrCode = "demo_table_01";

        db.Cafes.Add(new Cafe
        {
            Id = cafeId,
            Name = "Demo",
            Slug = "demo",
            PlanTier = PlanTier.Pro,
            DefaultTaxRate = 9m
        });
        db.Branches.Add(new Branch
        {
            Id = branchId,
            CafeId = cafeId,
            Name = "Main",
            IsActive = true,
            WelcomeText = "خوش آمدید",
            UpdatedAt = DateTime.UtcNow
        });
        db.MenuCategories.Add(new MenuCategory
        {
            Id = catId,
            CafeId = cafeId,
            Name = "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.Tables.Add(new Table
        {
            Id = tableId,
            CafeId = cafeId,
            BranchId = branchId,
            Number = "1",
            QrCode = qrCode,
            IsActive = true
        });
        db.SaveChanges();

        var config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?> { ["App:QrPublicBaseUrl"] = "https://meezi.test" })
            .Build();
        var kds = new NoOpKds();
        var branchMenu = new BranchMenuService(db);
        var identity = new BranchIdentityService(db);
        var tables = new TableService(db, config, kds, identity);
        var shifts = new ShiftService(db);
        var orders = new OrderService(db, kds, new NoOpSnappfood(), new NoOpDeliverySync(), shifts, TestServiceScopeFactory.Create(), new NoOpOrderNotificationService(), new NoOpInventoryService(), new NoOpLoyaltyService());
        var publicSvc = new PublicService(db, orders, new ReviewService(db), kds, branchMenu, identity);

        return (db, tables, publicSvc, cafeId, branchId, tableId, itemA, itemB, qrCode);
    }

    [Fact]
    public async Task ResolveQr_ValidCode_ReturnsBranchInfo()
    {
        var (_, tables, _, _, branchId, tableId, _, _, qrCode) = CreateFixture();
        var result = await tables.ResolveQrAsync(qrCode);
        Assert.NotNull(result);
        Assert.Equal(branchId, result!.BranchId);
        Assert.Equal(tableId, result.TableId);
        Assert.False(result.IsCleaning);
    }

    [Fact]
    public async Task ResolveQr_InvalidCode_ReturnsNull()
    {
        var (_, tables, _, _, _, _, _, _, _) = CreateFixture();
        Assert.Null(await tables.ResolveQrAsync("missing_code"));
    }

    [Fact]
    public async Task GetBranchMenu_ExcludesUnavailableItems()
    {
        var (db, _, pub, cafeId, branchId, _, itemA, itemB, _) = CreateFixture();
        var menuSvc = new BranchMenuService(db);
        await menuSvc.UpsertOverrideAsync(
            cafeId, branchId, itemB,
            new UpsertBranchMenuOverrideRequest(false, null),
            PlanTier.Pro, EmployeeRole.Manager, "u1");

        var menu = await pub.GetBranchMenuAsync(cafeId, branchId);
        var ids = menu!.Categories.SelectMany(c => c.Items).Select(i => i.Id).ToList();
        Assert.Contains(itemA, ids);
        Assert.DoesNotContain(itemB, ids);
    }

    [Fact]
    public async Task GetBranchMenu_AppliesBranchPriceOverride()
    {
        var (db, _, pub, cafeId, branchId, _, itemA, _, _) = CreateFixture();
        await new BranchMenuService(db).UpsertOverrideAsync(
            cafeId, branchId, itemA,
            new UpsertBranchMenuOverrideRequest(true, 88_000m),
            PlanTier.Pro, EmployeeRole.Manager, "u1");

        var item = (await pub.GetBranchMenuAsync(cafeId, branchId))!
            .Categories.SelectMany(c => c.Items)
            .First(i => i.Id == itemA);
        Assert.Equal(88_000m, item.Price);
    }

    [Fact]
    public async Task PlaceGuestOrder_ValidItems_CreatesOrderWithGuestQrSource()
    {
        var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
        var (data, code, _) = await pub.PlaceBranchGuestOrderAsync(
            cafeId,
            branchId,
            new PlaceGuestOrderRequest(tableId, "Guest", null, [new CreateOrderItemRequest(itemA, 2, null)]));

        Assert.Null(code);
        Assert.NotNull(data);
        var order = await db.Orders.Include(o => o.Items).FirstAsync(o => o.Id == data!.OrderId);
        Assert.Equal(OrderSource.GuestQr, order.Source);
        Assert.Equal(2, order.Items.Sum(i => i.Quantity));
    }

    [Fact]
    public async Task PlaceGuestOrder_MergesWithExistingOpenOrder()
    {
        var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
        await pub.PlaceBranchGuestOrderAsync(
            cafeId, branchId,
            new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));

        var (second, code, _) = await pub.PlaceBranchGuestOrderAsync(
            cafeId, branchId,
            new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));

        Assert.Null(code);
        Assert.Equal(1, await db.Orders.CountAsync(o => o.TableId == tableId));
        var order = await db.Orders.Include(o => o.Items).FirstAsync(o => o.Id == second!.OrderId);
        Assert.Equal(2, order.Items.Sum(i => i.Quantity));
    }

    [Fact]
    public async Task PlaceGuestOrder_CleaningTable_ReturnsTableCleaning()
    {
        var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
        var table = await db.Tables.FirstAsync(t => t.Id == tableId);
        table.IsCleaning = true;
        await db.SaveChangesAsync();

        var (_, code, _) = await pub.PlaceBranchGuestOrderAsync(
            cafeId, branchId,
            new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));

        Assert.Equal("TABLE_CLEANING", code);
    }

    [Fact]
    public async Task PlaceGuestOrder_InvalidMenuItem_ReturnsInvalidMenuItems()
    {
        var (_, _, pub, cafeId, branchId, tableId, _, _, _) = CreateFixture();
        var (_, code, _) = await pub.PlaceBranchGuestOrderAsync(
            cafeId, branchId,
            new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest("missing", 1, null)]));

        Assert.Equal("INVALID_MENU_ITEMS", code);
    }
}
