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

namespace Meezi.API.Tests;

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

    private static (AppDbContext Db, OrderService Orders, TableService Tables, string CafeId, string TableId, string MenuItemId)
        CreateFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-1";
        var branchId = "branch-1";
        var tableId = "table-1";
        var menuItemId = "item-1";
        var categoryId = "cat-1";
        var userId = "user-1";

        db.Cafes.Add(new Cafe { Id = cafeId, Name = "Test", Slug = "test", 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 = "Cashier",
            Phone = "09121111111",
            Role = EmployeeRole.Cashier
        });
        db.Tables.Add(new Table
        {
            Id = tableId,
            CafeId = cafeId,
            BranchId = branchId,
            Number = "5",
            QrCode = "qr1",
            Capacity = 4
        });
        db.MenuCategories.Add(new MenuCategory
        {
            Id = categoryId,
            CafeId = cafeId,
            Name = "Drinks",
            NameEn = "Drinks",
            SortOrder = 0
        });
        db.MenuItems.Add(new MenuItem
        {
            Id = menuItemId,
            CafeId = cafeId,
            CategoryId = categoryId,
            Name = "Espresso",
            NameEn = "Espresso",
            Price = 100_000m,
            IsAvailable = true
        });
        db.SaveChanges();

        var shifts = new ShiftService(db);
        shifts.OpenShiftAsync(cafeId, branchId, 0m, userId).GetAwaiter().GetResult();

        var kds = new NoOpKdsNotifier();
        var snapp = new NoOpSnappfood();
        var orders = new OrderService(db, kds, snapp, new NoOpDeliverySync(), shifts, TestServiceScopeFactory.Create(), new NoOpOrderNotificationService(), new NoOpInventoryService(), new NoOpLoyaltyService());
        var tables = new TableService(
            db,
            new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build(),
            new NullKdsNotifier(),
            new BranchIdentityService(db));

        return (db, orders, tables, cafeId, tableId, menuItemId);
    }

    private static CreateOrderRequest DineInRequest(string tableId, string menuItemId, int qty = 1) =>
        new(
            OrderType.DineIn,
            "branch-1",
            tableId,
            null,
            "Ali",
            "09121111111",
            null,
            null,
            [new CreateOrderItemRequest(menuItemId, qty, null)]);

    [Fact]
    public async Task CreateOrder_SameTable_MergesIntoSingleOpenOrder()
    {
        var (_, orders, _, cafeId, tableId, menuItemId) = CreateFixture();
        var tenant = new TestTenant(cafeId, "user-1");

        var first = await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId), default);
        var second = await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId, 2), default);

        Assert.True(first.Success);
        Assert.True(second.Success);
        Assert.Equal(first.Data!.Id, second.Data!.Id);
        Assert.Equal(3, second.Data!.Items.Sum(i => i.Quantity));
    }

    [Fact]
    public async Task AppendItems_IncreasesTotal()
    {
        var (_, orders, _, cafeId, tableId, menuItemId) = CreateFixture();
        var tenant = new TestTenant(cafeId, "user-1");

        var created = await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId), default);
        var append = await orders.AppendOrderItemsAsync(
            cafeId,
            created.Data!.Id,
            new AppendOrderItemsRequest([new CreateOrderItemRequest(menuItemId, 1, null)]),
            default);

        Assert.True(append.Success);
        Assert.Equal(200_000m, append.Data!.Subtotal);
    }

    [Fact]
    public async Task GetOpenOrders_SearchByPhone_FindsOrder()
    {
        var (_, orders, _, cafeId, tableId, menuItemId) = CreateFixture();
        var tenant = new TestTenant(cafeId, "user-1");
        await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId), default);

        var found = await orders.GetOpenOrdersAsync(cafeId, "09121111111", default);
        Assert.Single(found);
        Assert.Equal("09121111111", found[0].GuestPhone);
    }

    [Fact]
    public async Task Payment_CompletesOrder_TableBoardShowsFree()
    {
        var (_, orders, tables, cafeId, tableId, menuItemId) = CreateFixture();
        var tenant = new TestTenant(cafeId, "user-1");

        var created = await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId), default);
        var order = created.Data!;

        var paid = await orders.RecordPaymentsAsync(
            cafeId,
            order.Id,
            new RecordPaymentsRequest([new CreatePaymentRequest(PaymentMethod.Cash, order.Total, null)]),
            "user-1",
            default);
        Assert.True(paid.Success);

        var board = await tables.GetTableBoardAsync(cafeId, cancellationToken: default);
        var tile = board.First(t => t.Id == tableId);
        Assert.Equal(TableBoardStatus.Free, tile.Status);
    }

    [Fact]
    public async Task CleaningTable_BlocksNewOrder()
    {
        var (db, orders, tables, cafeId, tableId, menuItemId) = CreateFixture();
        var table = await db.Tables.FindAsync(tableId);
        table!.IsCleaning = true;
        await db.SaveChangesAsync();

        var tenant = new TestTenant(cafeId, "user-1");
        var result = await orders.CreateOrderAsync(cafeId, tenant, DineInRequest(tableId, menuItemId), default);

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

    private sealed class TestTenant(string cafeId, string userId) : ITenantContext
    {
        public string? CafeId => cafeId;
        public string? UserId => userId;
        public EmployeeRole? Role => EmployeeRole.Owner;
        public PlanTier? PlanTier => Core.Enums.PlanTier.Pro;
        public string? Language => "fa";
        public string? BranchId => null;
        public bool IsSystemAdmin => false;
        public bool IsAuthenticated => true;
    }

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

    private sealed class NoOpSnappfood : ISnappfoodClient
    {
        public Task AcknowledgeOrderAsync(string snappfoodOrderId, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
        public Task NotifyOrderDeliveredAsync(string snappfoodOrderId, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
        public Task NotifyOrderStatusAsync(string snappfoodOrderId, string status, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
    }
}
