using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using CMSMicroservice.WebApi.Common.Services;
using CMSMicroservice.Application.Common;
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders;
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport;
using DomainEnums = CMSMicroservice.Domain.Enums;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
using Mapster;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Linq;

namespace CMSMicroservice.WebApi.Services;

public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractBase
{
    private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
    private readonly ISender _sender;
    private readonly ICurrentUserService _currentUserService;
    private readonly IApplicationDbContext _context;
    private readonly IPaymentGatewayService _paymentGateway;
    private readonly ILogger<DiscountOrderService> _logger;
    private readonly IUserPaymentLock _paymentLock;

    public DiscountOrderService(
        IDispatchRequestToCQRS dispatchRequestToCQRS,
        ISender sender,
        ICurrentUserService currentUserService,
        IApplicationDbContext context,
        IPaymentGatewayService paymentGateway,
        ILogger<DiscountOrderService> logger,
        IUserPaymentLock paymentLock)
    {
        _dispatchRequestToCQRS = dispatchRequestToCQRS;
        _sender = sender;
        _currentUserService = currentUserService;
        _context = context;
        _paymentGateway = paymentGateway;
        _logger = logger;
        _paymentLock = paymentLock;
    }

    private long GetCurrentUserId()
    {
        if (long.TryParse(_currentUserService.UserId, out var uid) && uid > 0)
            return uid;
        throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است"));
    }

    public override async Task<PlaceOrderResponse> PlaceOrder(PlaceOrderRequest request, ServerCallContext context)
    {
        var command = new PlaceOrderCommand
        {
            UserId = GetCurrentUserId(),
            UserAddressId = request.UserAddressId,
            DiscountBalanceToUse = request.DiscountBalanceToUse
        };

        try
        {
            var result = await _sender.Send(command);
        
            var response = new PlaceOrderResponse
            {
                Success = result.Success,
                Message = result.Message ?? string.Empty,
                OrderId = result.OrderId ?? 0,
                GatewayAmount = result.GatewayAmountRequired,
            };
        
            if (!string.IsNullOrEmpty(result.PaymentUrl))
                response.PaymentUrl = result.PaymentUrl;
        
            return response;
        }
        catch (PaymentInProgressException ex)
        {
            return new PlaceOrderResponse
            {
                Success = false,
                Message = ex.Message
            };
        }
    }

    public override async Task<CompleteOrderPaymentResponse> CompleteOrderPayment(CompleteOrderPaymentRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<CompleteOrderPaymentRequest, CompleteOrderPaymentCommand, CompleteOrderPaymentResponse>(request, context);
    }

    public override async Task<UpdateOrderStatusResponse> UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
    {
        // Proto DeliveryStatus ≠ Domain DeliveryStatus — نگاشت صریح لازم است
        var command = new UpdateOrderStatusCommand
        {
            OrderId = request.OrderId,
            DeliveryStatus = MapProtoDeliveryToDomain(request.DeliveryStatus),
            TrackingCode = request.TrackingCode,
            AdminNotes = request.AdminNotes
        };
        var result = await _sender.Send(command, context.CancellationToken);
        return new UpdateOrderStatusResponse
        {
            Success = result.Success,
            Message = result.Message ?? string.Empty
        };
    }

    public override async Task<GetOrderByIdResponse> GetOrderById(GetOrderByIdRequest request, ServerCallContext context)
    {
        // اگر UserId ارسال شده > 0 باشد، آن را استفاده کن (customer lookup)
        // اگر UserId = 0 باشد، بدون فیلتر (admin access)
        long? filterUserId = request.UserId > 0 ? request.UserId : null;

        var query = new GetOrderByIdQuery
        {
            OrderId = request.OrderId,
            UserId = filterUserId
        };
        var result = await _sender.Send(query);
        if (result == null)
            throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد"));

        var response = new GetOrderByIdResponse
        {
            Id = result.Id,
            UserId = result.UserId,
            TotalPrice = result.TotalAmount,
            DiscountBalanceUsed = result.DiscountBalanceUsed,
            GatewayAmount = result.GatewayAmountPaid,
            PaymentCompleted = result.PaymentStatus == DomainEnums.PaymentStatus.Success,
            PaymentStatus = MapPaymentStatus(result.PaymentStatus),
            DeliveryStatus = MapDeliveryStatus(result.DeliveryStatus),
            Created = Timestamp.FromDateTime(DateTime.SpecifyKind(result.Created, DateTimeKind.Utc)),
        };

        if (result.TrackingCode != null) response.TrackingCode = result.TrackingCode;
        if (result.DeliveryDescription != null) response.Notes = result.DeliveryDescription;

        if (result.Address != null)
        {
            response.Address = new AddressInfo
            {
                Title = result.Address.Title ?? "",
                Address = result.Address.Address ?? "",
                PostalCode = result.Address.PostalCode ?? ""
            };
            if (!string.IsNullOrWhiteSpace(result.Address.Phone))
                response.Address.Phone = result.Address.Phone;
        }

        foreach (var item in result.Items)
        {
            response.Items.Add(new CMSMicroservice.Protobuf.Protos.DiscountOrder.OrderItemDto
            {
                ProductId = item.ProductId,
                ProductTitle = item.ProductTitle ?? "",
                UnitPrice = item.UnitPrice,
                MaxDiscountPercent = item.DiscountPercentUsed,
                Count = item.Count,
                TotalPrice = item.UnitPrice * item.Count,
                DiscountAmount = item.DiscountAmount,
                FinalPrice = item.FinalPrice,
                ImagePath = item.ImagePath ?? "",
                ThumbnailPath = item.ThumbnailPath ?? ""
            });
        }

        return response;
    }

    public override async Task<GetUserOrdersResponse> GetUserOrders(GetUserOrdersRequest request, ServerCallContext context)
    {
        var query = new GetUserOrdersQuery
        {
            UserId = GetCurrentUserId()
        };
        var result = await _sender.Send(query);

        var response = new GetUserOrdersResponse
        {
            MetaData = result.MetaData.Adapt<CMSMicroservice.Protobuf.Protos.MetaData>()
        };

        foreach (var o in result.Models)
        {
            var summary = new CMSMicroservice.Protobuf.Protos.DiscountOrder.OrderSummaryDto
            {
                Id = o.Id,
                TotalPrice = o.TotalAmount,
                DiscountBalanceUsed = o.DiscountBalanceUsed,
                GatewayAmount = o.GatewayAmountPaid,
                PaymentCompleted = o.PaymentStatus == DomainEnums.PaymentStatus.Success,
                PaymentStatus = MapPaymentStatus(o.PaymentStatus),
                DeliveryStatus = MapDeliveryStatus(o.DeliveryStatus),
                ItemsCount = o.ItemsCount,
                Created = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc)),
            };
            if (o.TrackingCode != null) summary.TrackingCode = o.TrackingCode;
            response.Models.Add(summary);
        }

        return response;
    }

    public override async Task<GetAllDiscountOrdersResponse> GetAllDiscountOrders(
        GetAllDiscountOrdersRequest request, ServerCallContext context)
    {
        // Proto PaymentStatus ≠ Domain PaymentStatus (مقادیر int متفاوت) — Adapt خام باعث باگ لیست/فیلتر می‌شود
        if (request.PaymentStatus is 3) // PAYMENT_REFUNDED — در دامنه وجود ندارد
        {
            return new GetAllDiscountOrdersResponse
            {
                MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
                {
                    TotalCount = 0,
                    PageSize = request.PageSize > 0 ? request.PageSize : 20,
                    CurrentPage = request.PageNumber > 0 ? request.PageNumber : 1,
                    TotalPage = 0
                }
            };
        }

        var query = new GetAllDiscountOrdersQuery
        {
            PaginationQuery = new Application.Common.Models.PaginationState
            {
                PageNumber = request.PageNumber > 0 ? request.PageNumber : 1,
                PageSize = request.PageSize > 0 ? request.PageSize : 20
            },
            UserId = request.UserId,
            UserMobile = request.UserMobile,
            TrackingCode = request.TrackingCode,
            FromDate = request.FromDate?.ToDateTime(),
            ToDate = request.ToDate?.ToDateTime(),
            MinAmount = request.MinAmount,
            MaxAmount = request.MaxAmount
        };

        if (request.PaymentStatus is int paymentStatus)
            query.PaymentStatus = MapProtoPaymentFilterToDomain(paymentStatus);

        if (request.DeliveryStatus is int deliveryStatus)
            query.DeliveryStatus = MapProtoDeliveryFilterToDomain(deliveryStatus);

        var result = await _sender.Send(query, context.CancellationToken);

        var response = new GetAllDiscountOrdersResponse
        {
            MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
            {
                TotalCount = result.MetaData.TotalCount,
                PageSize = result.MetaData.PageSize,
                CurrentPage = result.MetaData.CurrentPage,
                TotalPage = result.MetaData.TotalPage,
                HasPrevious = result.MetaData.HasPrevious,
                HasNext = result.MetaData.HasNext
            }
        };

        foreach (var o in result.Models)
        {
            var model = new CMSMicroservice.Protobuf.Protos.DiscountOrder.AdminOrderDto
            {
                Id = o.Id,
                UserId = o.UserId,
                UserFullName = o.UserFullName ?? string.Empty,
                UserMobile = o.UserMobile ?? string.Empty,
                TotalAmount = o.TotalAmount,
                DiscountBalanceUsed = o.DiscountBalanceUsed,
                GatewayAmountPaid = o.GatewayAmountPaid,
                VatAmount = o.VatAmount,
                PaymentStatus = MapPaymentStatus(o.PaymentStatus),
                DeliveryStatus = MapDeliveryStatus(o.DeliveryStatus),
                ItemsCount = o.ItemsCount,
                Created = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc))
            };

            if (o.PaymentDate.HasValue)
                model.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(o.PaymentDate.Value, DateTimeKind.Utc));
            if (o.LastModified.HasValue)
                model.LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(o.LastModified.Value, DateTimeKind.Utc));
            if (!string.IsNullOrWhiteSpace(o.ShippingAddress))
                model.ShippingAddress = o.ShippingAddress;
            if (!string.IsNullOrWhiteSpace(o.ReceiverName))
                model.ReceiverName = o.ReceiverName;
            if (!string.IsNullOrWhiteSpace(o.ReceiverMobile))
                model.ReceiverMobile = o.ReceiverMobile;
            if (!string.IsNullOrWhiteSpace(o.TrackingCode))
                model.TrackingCode = o.TrackingCode;
            if (!string.IsNullOrWhiteSpace(o.AdminNote))
                model.AdminNote = o.AdminNote;

            response.Models.Add(model);
        }

        return response;
    }

    public override async Task<GetDiscountSalesReportResponse> GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context)
    {
        return await _dispatchRequestToCQRS.Handle<GetDiscountSalesReportRequest, GetDiscountSalesReportQuery, GetDiscountSalesReportResponse>(request, context);
    }

    // ============= Customer Verify Discount Order Payment =============

    public override async Task<CustomerVerifyDiscountOrderPaymentResponse> CustomerVerifyDiscountOrderPayment(
        CustomerVerifyDiscountOrderPaymentRequest request, ServerCallContext context)
    {
        var orderOwner = await _context.DiscountOrders
            .Where(o => o.Id == request.OrderId)
            .Select(o => (long?)o.UserId)
            .FirstOrDefaultAsync(context.CancellationToken);

        if (orderOwner is null or 0)
        {
            return new CustomerVerifyDiscountOrderPaymentResponse
            {
                Success = false,
                Message = "سفارش یافت نشد",
                OrderId = request.OrderId
            };
        }

        var verifyKey = !string.IsNullOrEmpty(request.Authority)
            ? request.Authority
            : request.OrderId.ToString();

        try
        {
            return await _paymentLock.ExecuteAsync(
                PaymentLockScopes.Verify(orderOwner.Value, verifyKey),
                PaymentLockStrategy.WaitForRelease,
                ct => CustomerVerifyDiscountOrderPaymentCore(request, ct),
                context.CancellationToken);
        }
        catch (PaymentInProgressException ex)
        {
            return new CustomerVerifyDiscountOrderPaymentResponse
            {
                Success = false,
                Message = ex.Message,
                OrderId = request.OrderId
            };
        }
    }

    private async Task<CustomerVerifyDiscountOrderPaymentResponse> CustomerVerifyDiscountOrderPaymentCore(
        CustomerVerifyDiscountOrderPaymentRequest request,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "CustomerVerifyDiscountOrderPayment called: OrderId={OrderId}, Authority={Authority}, Status={Status}",
            request.OrderId, request.Authority, request.Status);

        try
        {
            // پیدا کردن سفارش و تراکنش
            var order = await _context.DiscountOrders
                .Include(o => o.OrderDetails)
                .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);

            if (order == null)
            {
                _logger.LogError("Order #{OrderId} not found", request.OrderId);
                return new CustomerVerifyDiscountOrderPaymentResponse
                {
                    Success = false,
                    Message = "سفارش یافت نشد",
                    OrderId = request.OrderId
                };
            }

            var transaction = order.TransactionId.HasValue
                ? await _context.Transactions.FirstOrDefaultAsync(
                    t => t.Id == order.TransactionId.Value, cancellationToken)
                : null;

            // تأیید پرداخت از درگاه
            bool paymentSuccess = false;
            string? refId = null;

            if (string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrEmpty(request.Authority))
            {
                // Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService)
                var verifyResult = await _paymentGateway.VerifyPaymentAsync(
                    request.Authority,
                    request.Status,
                    order.GatewayAmountPaid,
                    cancellationToken);

                paymentSuccess = verifyResult.IsSuccess;
                refId = verifyResult.TrackingCode ?? verifyResult.RefId;

                // آپدیت PaymentTransaction با نتیجه verify
                var paymentTx = await _context.PaymentTransactions
                    .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
                if (paymentTx != null)
                {
                    paymentTx.PaymentStatus = verifyResult.IsSuccess;
                    paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
                    paymentTx.VerificationStatusMessage = verifyResult.Message;
                    paymentTx.CardPan = verifyResult.CardPan;
                    paymentTx.CardHash = verifyResult.CardHash;
                    paymentTx.RefId = verifyResult.TrackingCode;
                    await _context.SaveChangesAsync(cancellationToken);
                }

                _logger.LogInformation(
                    "Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}",
                    request.OrderId, paymentSuccess, refId);
            }
            else
            {
                _logger.LogWarning("Payment cancelled by user for Order #{OrderId}", request.OrderId);
            }

            // تکمیل سفارش از طریق CQRS
            var completeResult = await _sender.Send(new CompleteOrderPaymentCommand
            {
                OrderId = request.OrderId,
                TransactionId = transaction?.Id ?? 0,
                PaymentSuccess = paymentSuccess,
                RefId = refId
            }, cancellationToken);

            return new CustomerVerifyDiscountOrderPaymentResponse
            {
                Success = paymentSuccess && completeResult.Success,
                Message = paymentSuccess && completeResult.Success
                    ? "پرداخت سفارش با موفقیت انجام شد"
                    : "پرداخت سفارش ناموفق بود",
                OrderId = request.OrderId
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Payment verify error for Order #{OrderId}", request.OrderId);
            return new CustomerVerifyDiscountOrderPaymentResponse
            {
                Success = false,
                Message = $"خطا در بررسی پرداخت: {ex.Message}",
                OrderId = request.OrderId
            };
        }
    }

    private static CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus MapPaymentStatus(DomainEnums.PaymentStatus status) => status switch
    {
        DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted,
        DomainEnums.PaymentStatus.Reject => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentFailed,
        DomainEnums.PaymentStatus.Pending => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending,
        _ => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending
    };

    /// <summary>
    /// فیلتر UI/proto → دامنه: PENDING=0, COMPLETED=1, FAILED=2
    /// </summary>
    private static DomainEnums.PaymentStatus MapProtoPaymentFilterToDomain(int protoPaymentStatus) => protoPaymentStatus switch
    {
        1 => DomainEnums.PaymentStatus.Success,   // PAYMENT_COMPLETED
        2 => DomainEnums.PaymentStatus.Reject,    // PAYMENT_FAILED
        _ => DomainEnums.PaymentStatus.Pending    // PAYMENT_PENDING (0) و پیش‌فرض
    };

    private static DeliveryStatus MapDeliveryStatus(DomainEnums.DeliveryStatus status) => status switch
    {
        DomainEnums.DeliveryStatus.None => DeliveryStatus.DeliveryPending,
        DomainEnums.DeliveryStatus.Pending => DeliveryStatus.DeliveryProcessing,
        DomainEnums.DeliveryStatus.InTransit => DeliveryStatus.DeliveryShipped,
        DomainEnums.DeliveryStatus.Delivered => DeliveryStatus.DeliveryDelivered,
        DomainEnums.DeliveryStatus.Cancelled => DeliveryStatus.DeliveryCancelled,
        DomainEnums.DeliveryStatus.ReadyForOfficePickup => DeliveryStatus.DeliveryReadyForOffice,
        DomainEnums.DeliveryStatus.Returned => DeliveryStatus.DeliveryReturned,
        _ => DeliveryStatus.DeliveryPending
    };

    /// <summary>
    /// فیلتر UI/proto → دامنه DeliveryStatus
    /// </summary>
    private static DomainEnums.DeliveryStatus MapProtoDeliveryFilterToDomain(int protoDeliveryStatus) => protoDeliveryStatus switch
    {
        1 => DomainEnums.DeliveryStatus.Pending,                // DELIVERY_PROCESSING
        2 => DomainEnums.DeliveryStatus.InTransit,              // DELIVERY_SHIPPED
        3 => DomainEnums.DeliveryStatus.Delivered,              // DELIVERY_DELIVERED
        4 => DomainEnums.DeliveryStatus.Cancelled,              // DELIVERY_CANCELLED
        5 => DomainEnums.DeliveryStatus.ReadyForOfficePickup,   // DELIVERY_READY_FOR_OFFICE
        6 => DomainEnums.DeliveryStatus.Returned,               // DELIVERY_RETURNED
        _ => DomainEnums.DeliveryStatus.None                    // DELIVERY_PENDING (0)
    };

    private static DomainEnums.DeliveryStatus MapProtoDeliveryToDomain(DeliveryStatus status) => status switch
    {
        DeliveryStatus.DeliveryProcessing => DomainEnums.DeliveryStatus.Pending,
        DeliveryStatus.DeliveryShipped => DomainEnums.DeliveryStatus.InTransit,
        DeliveryStatus.DeliveryDelivered => DomainEnums.DeliveryStatus.Delivered,
        DeliveryStatus.DeliveryCancelled => DomainEnums.DeliveryStatus.Cancelled,
        DeliveryStatus.DeliveryReadyForOffice => DomainEnums.DeliveryStatus.ReadyForOfficePickup,
        DeliveryStatus.DeliveryReturned => DomainEnums.DeliveryStatus.Returned,
        _ => DomainEnums.DeliveryStatus.None
    };
}
