using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Commerce;
using Tiku.Domain.Commerce;

namespace Tiku.Infrastructure.Commerce;

public sealed partial class CommerceService
{
    public async Task<PaymentNotificationProcessResult> ProcessPaymentNotificationAsync(
        Guid tenantId,
        string provider,
        IReadOnlyDictionary<string, string> headers,
        string rawBody,
        JsonElement body,
        CancellationToken cancellationToken = default)
    {
        var normalizedProvider = NormalizeProvider(provider);
        var notification = await paymentGateway.ParsePaymentNotificationAsync(
            normalizedProvider,
            new PaymentNotificationRequest(
                tenantId,
                normalizedProvider,
                headers,
                rawBody,
                body),
            cancellationToken);

        if (!notification.SignatureValid)
            throw new CommerceException("Payment notification signature is invalid.", "payment_signature_invalid");

        var alreadyProcessed = await dbContext.PaymentEvents.AnyAsync(
            item =>
                item.Provider == normalizedProvider &&
                item.EventId == notification.EventId &&
                item.ProcessedAt != null,
            cancellationToken);
        if (alreadyProcessed)
            return new PaymentNotificationProcessResult(
                normalizedProvider,
                notification.EventId,
                notification.OrderNo,
                "processed",
                true);

        var order = await dbContext.Orders
                        .SingleOrDefaultAsync(item =>
                                item.TenantId == tenantId &&
                                item.OrderNo == notification.OrderNo,
                            cancellationToken)
                    ?? throw new CommerceException("Order was not found.", "order_not_found");

        if (order.UserId is null) throw new CommerceException("Order does not belong to a user.", "order_user_missing");

        if (order.AmountCents != notification.AmountCents)
        {
            dbContext.PaymentEvents.Add(new PaymentEvent
            {
                TenantId = tenantId,
                Provider = normalizedProvider,
                EventType = notification.EventType,
                EventId = notification.EventId,
                SignatureValid = true,
                Payload = notification.RawPayload,
                Error = "payment_amount_mismatch"
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            throw new CommerceException("Payment amount does not match order amount.", "payment_amount_mismatch");
        }

        var payment = await dbContext.Payments
            .Where(item =>
                item.TenantId == tenantId &&
                item.OrderId == order.Id &&
                item.Provider == normalizedProvider)
            .OrderByDescending(item => item.CreatedAt)
            .FirstOrDefaultAsync(cancellationToken);
        if (payment is null)
        {
            payment = new Payment
            {
                TenantId = tenantId,
                OrderId = order.Id,
                Provider = normalizedProvider,
                Method = order.PayMethod,
                Status = PaymentStatus.Pending,
                AmountCents = order.AmountCents
            };
            dbContext.Payments.Add(payment);
        }

        if (notification.Paid && order.Status == OrderStatus.Pending)
            await MarkPaidAsync(
                new CommerceActor(tenantId, order.UserId.Value),
                order,
                payment,
                notification.ProviderTradeNo,
                notification.RawPayload,
                notification.EventType,
                notification.EventId,
                notification.SignatureValid,
                notification.PaidAt,
                cancellationToken);
        else
            dbContext.PaymentEvents.Add(new PaymentEvent
            {
                TenantId = tenantId,
                PaymentId = payment.Id,
                Provider = normalizedProvider,
                EventType = notification.EventType,
                EventId = notification.EventId,
                SignatureValid = notification.SignatureValid,
                Payload = notification.RawPayload,
                ProcessedAt = DateTimeOffset.UtcNow
            });

        await dbContext.SaveChangesAsync(cancellationToken);
        return new PaymentNotificationProcessResult(
            normalizedProvider,
            notification.EventId,
            notification.OrderNo,
            "processed",
            false);
    }
}