using CMSMicroservice.Protobuf.Protos.Inventory;
using CMSMicroservice.WebApi.Common.Services;
using CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses;
using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
using CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement;
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements;
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem;
using CMSMicroservice.Application.Common.Interfaces;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace CMSMicroservice.WebApi.Services;

public class InventoryService : InventoryContract.InventoryContractBase
{
    private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
    private readonly IMediator _mediator;
    private readonly IApplicationDbContext _context;

    public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator, IApplicationDbContext context)
    {
        _dispatchRequestToCQRS = dispatchRequestToCQRS;
        _mediator = mediator;
        _context = context;
    }

    // ========== Warehouse Management ==========

    public override async Task<CreateWarehouseResponse> CreateWarehouse(CreateWarehouseRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<CreateWarehouseRequest, CreateWarehouseCommand, CreateWarehouseResponse>(request, context);
    }

    public override async Task<Empty> UpdateWarehouse(UpdateWarehouseRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<UpdateWarehouseRequest, UpdateWarehouseCommand>(request, context);
    }

    public override async Task<Empty> DeleteWarehouse(DeleteWarehouseRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<DeleteWarehouseRequest, DeleteWarehouseCommand>(request, context);
    }

    public override async Task<GetWarehouseResponse> GetWarehouse(GetWarehouseRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetWarehouseRequest, GetWarehouseQuery, GetWarehouseResponse>(request, context);
    }

    public override async Task<GetAllWarehousesResponse> GetAllWarehouses(GetAllWarehousesRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetAllWarehousesRequest, GetAllWarehousesQuery, GetAllWarehousesResponse>(request, context);
    }

    public override async Task<Empty> SetDefaultWarehouse(SetDefaultWarehouseRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<SetDefaultWarehouseRequest, SetDefaultWarehouseCommand>(request, context);
    }

    // ========== Inventory Item Management ==========

    public override async Task<GetInventoryItemResponse> GetInventoryItem(GetInventoryItemRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetInventoryItemRequest, GetInventoryItemQuery, GetInventoryItemResponse>(request, context);
    }

    public override async Task<GetInventoryByProductResponse> GetInventoryByProduct(GetInventoryByProductRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetInventoryByProductRequest, GetInventoryByProductQuery, GetInventoryByProductResponse>(request, context);
    }

    public override async Task<GetAllInventoryItemsResponse> GetAllInventoryItems(GetAllInventoryItemsRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetAllInventoryItemsRequest, GetAllInventoryItemsQuery, GetAllInventoryItemsResponse>(request, context);
    }

    public override async Task<GetLowStockItemsResponse> GetLowStockItems(GetLowStockItemsRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetLowStockItemsRequest, GetLowStockItemsQuery, GetLowStockItemsResponse>(request, context);
    }

    public override async Task<Empty> UpdateInventorySettings(UpdateInventorySettingsRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<UpdateInventorySettingsRequest, UpdateInventoryItemCommand>(request, context);
    }

    // ========== Stock Operations ==========

    public override async Task<AddStockResponse> AddStock(AddStockRequest request, ServerCallContext context)
    {
        // Lookup InventoryItem by ProductId + ProductType
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
        {
            throw new RpcException(new Status(StatusCode.NotFound, 
                $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}"));
        }

        // Execute IncreaseInventoryCommand
        var response = await _mediator.Send(
            new IncreaseInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                ReferenceNumber = request.ReferenceNumber
            },
            context.CancellationToken);

        return new AddStockResponse
        {
            InventoryItemId = inventoryItem.Id,
            NewQuantity = response.NewQuantity
        };
    }

    public override async Task<AdjustStockResponse> AdjustStock(AdjustStockRequest request, ServerCallContext context)
    {
        // Lookup InventoryItem
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
        {
            throw new RpcException(new Status(StatusCode.NotFound, 
                $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}"));
        }

        // Determine if increase or decrease
        var difference = request.NewQuantity - inventoryItem.Quantity;

        if (difference > 0)
        {
            await _mediator.Send(
                new IncreaseInventoryCommand
                {
                    Id = inventoryItem.Id,
                    Quantity = difference,
                    ReferenceNumber = request.ReferenceNumber
                },
                context.CancellationToken);

            return new AdjustStockResponse
            {
                PreviousQuantity = inventoryItem.Quantity,
                NewQuantity = request.NewQuantity,
                Difference = difference
            };
        }
        else if (difference < 0)
        {
            await _mediator.Send(
                new ReduceInventoryCommand
                {
                    Id = inventoryItem.Id,
                    Quantity = Math.Abs(difference),
                    FromReserved = false,
                    ReferenceNumber = request.ReferenceNumber,
                    MovementType = Domain.Enums.StockMovementType.AdjustmentMinus,
                    Note = "Stock adjustment (decrease)"
                },
                context.CancellationToken);

            return new AdjustStockResponse
            {
                PreviousQuantity = inventoryItem.Quantity,
                NewQuantity = request.NewQuantity,
                Difference = difference
            };
        }
        else
        {
            return new AdjustStockResponse
            {
                PreviousQuantity = inventoryItem.Quantity,
                NewQuantity = inventoryItem.Quantity,
                Difference = 0
            };
        }
    }

    public override async Task<ReserveStockResponse> ReserveStock(ReserveStockRequest request, ServerCallContext context)
    {
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
            return new ReserveStockResponse { Success = false, Message = "آیتم موجودی یافت نشد", AvailableQuantity = 0 };

        var available = inventoryItem.Quantity - inventoryItem.ReservedQuantity;
        if (available < request.Quantity)
            return new ReserveStockResponse { Success = false, Message = $"موجودی کافی نیست. موجود: {available}", AvailableQuantity = available };

        await _mediator.Send(
            new ReserveInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                ReferenceNumber = request.OrderId != null ? $"ORDER-{request.OrderId.Value}" : $"RESERVE-{DateTime.UtcNow.Ticks}"
            },
            context.CancellationToken);

        return new ReserveStockResponse { Success = true, Message = "رزرو با موفقیت انجام شد", AvailableQuantity = available - request.Quantity };
    }

    public override async Task<Empty> ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context)
    {
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
            throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));

        await _mediator.Send(
            new ReleaseReservedInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                ReferenceNumber = request.OrderId != null ? $"RELEASE-ORDER-{request.OrderId.Value}" : $"RELEASE-{DateTime.UtcNow.Ticks}"
            },
            context.CancellationToken);

        return new Empty();
    }

    public override async Task<Empty> ConfirmSale(ConfirmSaleRequest request, ServerCallContext context)
    {
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
            throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));

        await _mediator.Send(
            new ReduceInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                FromReserved = request.FromReservation,
                ReferenceNumber = request.OrderId != null ? $"SALE-ORDER-{request.OrderId.Value}" : $"SALE-{DateTime.UtcNow.Ticks}"
            },
            context.CancellationToken);

        return new Empty();
    }

    public override async Task<ProcessReturnResponse> ProcessReturn(ProcessReturnRequest request, ServerCallContext context)
    {
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
            throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));

        var result = await _mediator.Send(
            new IncreaseInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                ReferenceNumber = request.OrderId != null ? $"RETURN-ORDER-{request.OrderId.Value}" : $"RETURN-{DateTime.UtcNow.Ticks}"
            },
            context.CancellationToken);

        return new ProcessReturnResponse { NewQuantity = result.NewQuantity };
    }

    public override async Task<Empty> RecordLoss(RecordLossRequest request, ServerCallContext context)
    {
        // Lookup InventoryItem by ProductId + ProductType
        var inventoryItem = await _mediator.Send(
            new GetInventoryByProductQuery
            {
                ProductId = request.ProductId,
                ProductType = (Domain.Enums.ProductType)request.ProductType
            },
            context.CancellationToken);

        if (inventoryItem == null)
        {
            throw new RpcException(new Status(StatusCode.NotFound, 
                $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}"));
        }

        // Execute ReduceInventoryCommand to record the loss
        await _mediator.Send(
            new ReduceInventoryCommand
            {
                Id = inventoryItem.Id,
                Quantity = request.Quantity,
                FromReserved = false,
                ReferenceNumber = request.ReferenceNumber ?? $"LOSS-{DateTime.UtcNow:yyyyMMddHHmmss}",
                MovementType = (Domain.Enums.StockMovementType)request.LossType,
                Note = string.IsNullOrWhiteSpace(request.Reason) ? null : request.Reason
            },
            context.CancellationToken);

        return new Empty();
    }

    // ========== Bulk Operations ==========

    public override async Task<BulkAddStockResponse> BulkAddStock(BulkAddStockRequest request, ServerCallContext context)
    {
        var response = new BulkAddStockResponse();
        
        foreach (var item in request.Items)
        {
            try
            {
                var inventoryItem = await _mediator.Send(
                    new GetInventoryByProductQuery
                    {
                        ProductId = item.ProductId,
                        ProductType = (Domain.Enums.ProductType)item.ProductType
                    },
                    context.CancellationToken);

                if (inventoryItem == null)
                {
                    response.FailedCount++;
                    response.Errors.Add($"ProductId={item.ProductId}: آیتم موجودی یافت نشد");
                    continue;
                }

                await _mediator.Send(
                    new IncreaseInventoryCommand
                    {
                        Id = inventoryItem.Id,
                        Quantity = item.Quantity,
                        ReferenceNumber = request.ReferenceNumber ?? $"BULK-{DateTime.UtcNow.Ticks}"
                    },
                    context.CancellationToken);

                response.SuccessCount++;
            }
            catch (Exception ex)
            {
                response.FailedCount++;
                response.Errors.Add($"ProductId={item.ProductId}: {ex.Message}");
            }
        }
        
        return response;
    }

    // ========== Stock Movements ==========

    public override async Task<GetStockMovementsResponse> GetStockMovements(GetStockMovementsRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetStockMovementsRequest, GetStockMovementsQuery, GetStockMovementsResponse>(request, context);
    }

    public override async Task<GetStockMovementsByInventoryItemResponse> GetStockMovementsByInventoryItem(GetStockMovementsByInventoryItemRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetStockMovementsByInventoryItemRequest, GetStockMovementsByInventoryItemQuery, GetStockMovementsByInventoryItemResponse>(request, context);
    }

    // ========== Reports ==========

    public override async Task<GetInventorySummaryResponse> GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context)
    {
        var query = _context.InventoryItems
            .Include(i => i.Product)
            .Include(i => i.DiscountProduct)
            .Where(i => !i.IsDeleted);
        
        if (request.WarehouseId != null)
            query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
        
        var items = await query.ToListAsync(context.CancellationToken);
        
        var regularProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.RegularProduct).ToList();
        var discountProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.DiscountProduct).ToList();
        
        long totalStockValue = items.Sum(i =>
        {
            long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
            return (long)i.Quantity * unitPrice;
        });
        
        return new GetInventorySummaryResponse
        {
            TotalProducts = regularProducts.Count,
            TotalDiscountProducts = discountProducts.Count,
            TotalQuantity = items.Sum(i => i.Quantity),
            TotalReserved = items.Sum(i => i.ReservedQuantity),
            LowStockCount = items.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0),
            OutOfStockCount = items.Count(i => i.Quantity == 0),
            TotalStockValue = totalStockValue
        };
    }

    public override async Task<GetStockValueReportResponse> GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context)
    {
        var query = _context.InventoryItems
            .Include(i => i.Product)
            .Include(i => i.DiscountProduct)
            .Where(i => !i.IsDeleted);
        
        if (request.WarehouseId != null)
            query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
        
        if (request.ProductType != ProductType.Unspecified)
            query = query.Where(i => i.ProductType == (Domain.Enums.ProductType)request.ProductType);
        
        var dbItems = await query.ToListAsync(context.CancellationToken);
        
        var items = dbItems.Select(i =>
        {
            long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
            string title = i.Product?.Title ?? i.DiscountProduct?.Title ?? string.Empty;
            return new StockValueItem
            {
                ProductId = i.ProductId ?? i.DiscountProductId ?? 0,
                ProductTitle = title,
                ProductType = (ProductType)i.ProductType,
                Quantity = i.Quantity,
                UnitPrice = unitPrice,
                TotalValue = (long)i.Quantity * unitPrice
            };
        }).ToList();
        
        return new GetStockValueReportResponse
        {
            Items = { items },
            TotalValue = items.Sum(i => i.TotalValue),
            TotalItems = items.Count
        };
    }
}
