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

namespace Meezi.API.Tests;

public class OrderVoidTransferTests
{
    private static (
        AppDbContext Db,
        OrderService Orders,
        string CafeId,
        string Table1Id,
        string Table2Id,
        string MenuItemId,
        TenantContext Tenant)
        CreateFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        var db = new AppDbContext(options);

        var cafeId = "cafe-void";
        var table1 = "table-1";
        var table2 = "table-2";
        var menuItemId = "item-1";
        var categoryId = "cat-1";
        var managerId = "emp-manager";

        db.Cafes.Add(new Cafe { Id = cafeId, Name = "Test", Slug = "test-void", PlanTier = PlanTier.Pro });
        db.Tables.Add(new Table { Id = table1, CafeId = cafeId, Number = "1", QrCode = "qr1", Capacity = 4 });
        db.Tables.Add(new Table { Id = table2, CafeId = cafeId, Number = "2", QrCode = "qr2", 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 = "Tea",
            NameEn = "Tea",
            Price = 50_000m,
            IsAvailable = true
        });
        db.Employees.Add(new Employee
        {
            Id = managerId,
            CafeId = cafeId,
            Name = "Manager",
            Phone = "09120000000",
            Role = EmployeeRole.Manager
        });
        db.SaveChanges();

        var tenant = new TenantContext
        {
            CafeId = cafeId,
            UserId = managerId,
            Role = EmployeeRole.Manager,
            PlanTier = PlanTier.Pro,
            Language = "fa"
        };

        var orders = new OrderService(db, new NoOpKdsNotifier(), new NoOpSnappfood(), new NoOpDeliverySync(), new NoOpShiftService(), TestServiceScopeFactory.Create(), new NoOpOrderNotificationService(), new NoOpInventoryService(), new NoOpLoyaltyService());
        return (db, orders, cafeId, table1, table2, menuItemId, tenant);
    }

    private static async Task<OrderDto> CreateOpenOrderAsync(
        OrderService orders,
        string cafeId,
        TenantContext tenant,
        string tableId,
        string menuItemId,
        int qty = 1)
    {
        var result = await orders.CreateOrderAsync(
            cafeId,
            tenant,
            new CreateOrderRequest(
                OrderType.DineIn,
                null,
                tableId,
                null,
                "Guest",
                null,
                null,
                null,
                [new CreateOrderItemRequest(menuItemId, qty, null)]));
        Assert.True(result.Success);
        return result.Data!;
    }

    [Fact]
    public async Task VoidItem_ReducesOrderTotal()
    {
        var (_, orders, cafeId, table1, _, menuItemId, tenant) = CreateFixture();
        var order = await CreateOpenOrderAsync(orders, cafeId, tenant, table1, menuItemId, 2);
        var itemId = order.Items[0].Id;
        var before = order.Total;

        var result = await orders.VoidOrderItemAsync(cafeId, order.Id, itemId, tenant.UserId!);

        Assert.True(result.Success);
        Assert.True(result.Data!.Items.First(i => i.Id == itemId).IsVoided);
        Assert.True(result.Data.Total < before);
    }

    [Fact]
    public async Task VoidItem_AlreadyVoided_ReturnsError()
    {
        var (_, orders, cafeId, table1, _, menuItemId, tenant) = CreateFixture();
        var order = await CreateOpenOrderAsync(orders, cafeId, tenant, table1, menuItemId);
        var itemId = order.Items[0].Id;

        await orders.VoidOrderItemAsync(cafeId, order.Id, itemId, tenant.UserId!);
        var second = await orders.VoidOrderItemAsync(cafeId, order.Id, itemId, tenant.UserId!);

        Assert.False(second.Success);
        Assert.Equal("ITEM_ALREADY_VOIDED", second.ErrorCode);
    }

    [Fact]
    public async Task TransferTable_MovesOrderToFreeTable()
    {
        var (_, orders, cafeId, table1, table2, menuItemId, tenant) = CreateFixture();
        var order = await CreateOpenOrderAsync(orders, cafeId, tenant, table1, menuItemId);

        var result = await orders.TransferTableAsync(cafeId, order.Id, table2);

        Assert.True(result.Success);
        Assert.Equal(table2, result.Data!.TableId);
    }

    [Fact]
    public async Task TransferTable_ToOccupiedTable_ReturnsTableOccupied()
    {
        var (_, orders, cafeId, table1, table2, menuItemId, tenant) = CreateFixture();
        await CreateOpenOrderAsync(orders, cafeId, tenant, table1, menuItemId);
        var order2 = await CreateOpenOrderAsync(orders, cafeId, tenant, table2, menuItemId);

        var result = await orders.TransferTableAsync(cafeId, order2.Id, table1);

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

    [Fact]
    public async Task TransferTable_ToCleaningTable_ReturnsTableCleaning()
    {
        var (db, orders, cafeId, table1, table2, menuItemId, tenant) = CreateFixture();
        var order = await CreateOpenOrderAsync(orders, cafeId, tenant, table1, menuItemId);

        var table = await db.Tables.FirstAsync(t => t.Id == table2);
        table.IsCleaning = true;
        await db.SaveChangesAsync();

        var result = await orders.TransferTableAsync(cafeId, order.Id, table2);

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

    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;
    }

    private sealed class NoOpShiftService : IShiftService
    {
        public Task<ShiftServiceResult<ShiftDto>> OpenShiftAsync(string cafeId, string branchId, decimal openingCash, string userId, CancellationToken cancellationToken = default) =>
            throw new NotImplementedException();
        public Task<ShiftServiceResult<ShiftDto>> CloseShiftAsync(string cafeId, string shiftId, decimal closingCash, string userId, CancellationToken cancellationToken = default) =>
            throw new NotImplementedException();
        public Task<ShiftDto?> GetCurrentShiftAsync(string cafeId, string branchId, CancellationToken cancellationToken = default) =>
            Task.FromResult<ShiftDto?>(null);
        public Task<IReadOnlyList<CashTransactionDto>?> GetTransactionsAsync(string cafeId, string shiftId, CancellationToken cancellationToken = default) =>
            throw new NotImplementedException();
        public Task<ShiftServiceResult<CashTransactionDto>> RecordTransactionAsync(string cafeId, string shiftId, CashTransactionType type, PaymentMethod method, decimal amount, string createdByUserId, string? referenceId = null, string? note = null, CancellationToken cancellationToken = default) =>
            throw new NotImplementedException();
        public Task<ShiftServiceResult<Shift>> RequireOpenShiftForBranchAsync(string cafeId, string branchId, CancellationToken cancellationToken = default) =>
            Task.FromResult(new ShiftServiceResult<Shift>(true, new Shift { Id = "shift-test", CafeId = cafeId, BranchId = branchId }));
    }
}
