using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Commerce;
using Tiku.Application.PlatformBilling;
using Tiku.Domain.Common;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class PlatformBillingCallbackTests
{
    [Theory]
    [InlineData("wechat_pay", "application/json", "{\"code\":\"SUCCESS\"")]
    [InlineData("alipay", "text/plain", "success")]
    public async Task Valid_provider_callback_settles_once(
        string provider,
        string expectedMediaType,
        string expectedBody)
    {
        var fixture = CreateFixture(provider);
        var gateway = new FakePlatformBillingPaymentGateway(fixture.Notification);
        await using var factory = new ApiTestFactory(platformBillingPaymentGateway: gateway);
        await factory.SeedAsync(fixture.Entities);
        await PublishVersionAsync(factory, fixture.VersionId);
        using var client = factory.CreateClient();

        var first = await client.PostAsJsonAsync($"/api/integrations/platform-billing/callbacks/{provider}",
            new { eventId = fixture.Notification.EventId });
        var repeated = await client.PostAsJsonAsync($"/api/integrations/platform-billing/callbacks/{provider}",
            new { eventId = fixture.Notification.EventId });

        Assert.Equal(HttpStatusCode.OK, first.StatusCode);
        Assert.Equal(HttpStatusCode.OK, repeated.StatusCode);
        Assert.Equal(expectedMediaType, first.Content.Headers.ContentType?.MediaType);
        Assert.Contains(expectedBody, await first.Content.ReadAsStringAsync(), StringComparison.Ordinal);
        Assert.All(gateway.ParsedProviders, value => Assert.Equal(provider, value));

        using var scope = factory.CreateSystemScope("Verify platform payment callback idempotency");
        var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Equal(PlatformBillingPaymentStatus.Succeeded,
            (await db.PlatformBillingPayments.AsNoTracking().SingleAsync(value => value.Id == fixture.PaymentId))
            .Status);
        Assert.Single(await db.PlatformBillingPaymentEvents.AsNoTracking()
            .Where(value => value.PaymentId == fixture.PaymentId)
            .ToArrayAsync());
        Assert.Single(await db.PlatformBillingInvoices.AsNoTracking()
            .Where(value => value.OrderId == fixture.OrderId)
            .ToArrayAsync());
        Assert.Single(await db.TenantSaasSubscriptions.AsNoTracking()
            .Where(value => value.TenantId == fixture.TenantId)
            .ToArrayAsync());
    }

    [Fact]
    public async Task Invalid_signature_is_rejected_without_settlement()
    {
        var fixture = CreateFixture(PaymentProviders.WechatPay);
        var invalid = fixture.Notification with { SignatureValid = false };
        await using var factory = new ApiTestFactory(
            platformBillingPaymentGateway: new FakePlatformBillingPaymentGateway(invalid));
        await factory.SeedAsync(fixture.Entities);
        await PublishVersionAsync(factory, fixture.VersionId);
        using var client = factory.CreateClient();

        var response = await client.PostAsJsonAsync(
            "/api/integrations/platform-billing/callbacks/wechat_pay",
            new { eventId = invalid.EventId });

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        Assert.Equal("platform_billing_notification_invalid", await ReadCodeAsync(response));
        await AssertPaymentRemainsPendingAsync(factory, fixture.PaymentId);
    }

    [Fact]
    public async Task Callback_amount_mismatch_is_rejected_without_settlement()
    {
        var fixture = CreateFixture(PaymentProviders.Alipay);
        var mismatched = fixture.Notification with { AmountCents = fixture.Notification.AmountCents + 1 };
        await using var factory = new ApiTestFactory(
            platformBillingPaymentGateway: new FakePlatformBillingPaymentGateway(mismatched));
        await factory.SeedAsync(fixture.Entities);
        await PublishVersionAsync(factory, fixture.VersionId);
        using var client = factory.CreateClient();

        var response = await client.PostAsJsonAsync(
            "/api/integrations/platform-billing/callbacks/alipay",
            new { eventId = mismatched.EventId });

        Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
        Assert.Equal("platform_billing_payment_amount_mismatch", await ReadCodeAsync(response));
        await AssertPaymentRemainsPendingAsync(factory, fixture.PaymentId);
    }

    private static CallbackFixture CreateFixture(string provider)
    {
        const int amountCents = 8_800;
        var tenantId = Guid.NewGuid();
        var feature = new SaasFeature
        {
            Code = $"callback.feature.{Guid.NewGuid():N}",
            Name = "Callback feature",
            Category = "integration",
            Status = SaasFeatureStatus.Active
        };
        var offering = new SaasOffering
        {
            Id = Guid.NewGuid(),
            Code = $"callback-offering-{Guid.NewGuid():N}",
            Name = "Callback offering",
            Type = SaasOfferingType.BasePlan,
            Status = SaasOfferingStatus.Active
        };
        var version = new SaasOfferingVersion
        {
            Id = Guid.NewGuid(),
            OfferingId = offering.Id,
            Version = 1,
            Status = SaasOfferingVersionStatus.Draft,
            BillingCycle = PlatformBillingCycle.Monthly,
            OriginalAmountCents = amountCents,
            AmountCents = amountCents,
            Currency = "CNY",
            EffectiveAt = DateTimeOffset.UtcNow.AddDays(-1)
        };
        var order = new PlatformBillingOrder
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            QuoteId = Guid.NewGuid(),
            OrderNo = $"SO{Guid.NewGuid():N}",
            IdempotencyKey = $"order-{Guid.NewGuid():N}",
            Purpose = PlatformBillingOrderPurpose.NewSubscription,
            Status = PlatformBillingOrderStatus.PendingPayment,
            OriginalAmountCents = amountCents,
            TotalAmountCents = amountCents,
            Currency = "CNY",
            ExpiresAt = DateTimeOffset.UtcNow.AddHours(1),
            Snapshot = JsonDefaults.Object()
        };
        var quote = new PlatformBillingQuote
        {
            Id = order.QuoteId,
            TenantId = tenantId,
            QuoteNo = $"SQ{Guid.NewGuid():N}",
            IdempotencyKey = $"quote-{Guid.NewGuid():N}",
            Purpose = PlatformBillingOrderPurpose.NewSubscription,
            Status = PlatformBillingQuoteStatus.Converted,
            OriginalAmountCents = amountCents,
            TotalAmountCents = amountCents,
            Currency = "CNY",
            ExpiresAt = DateTimeOffset.UtcNow.AddHours(1),
            FeatureSnapshot = JsonDefaults.Array(),
            LimitSnapshot = JsonDefaults.Object()
        };
        var orderItem = new PlatformBillingOrderItem
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            OrderId = order.Id,
            OfferingVersionId = version.Id,
            ItemType = PlatformBillingItemType.BasePlan,
            UnitAmountCents = amountCents,
            AmountCents = amountCents,
            Snapshot = JsonDefaults.Object()
        };
        var payment = new PlatformBillingPayment
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            OrderId = order.Id,
            PaymentNo = $"SP{Guid.NewGuid():N}",
            IdempotencyKey = $"payment-{Guid.NewGuid():N}",
            Provider = provider,
            Method = provider == PaymentProviders.Alipay ? "web" : "jsapi",
            AmountCents = amountCents,
            Status = PlatformBillingPaymentStatus.Pending
        };
        var notification = new PaymentNotificationResult(
            provider,
            "payment.succeeded",
            $"event-{Guid.NewGuid():N}",
            order.OrderNo,
            $"trade-{Guid.NewGuid():N}",
            amountCents,
            true,
            true,
            DateTimeOffset.UtcNow,
            JsonDefaults.Object());
        object[] entities =
        [
            new Tenant
            {
                Id = tenantId,
                Slug = $"callback-{tenantId:N}",
                Name = "Callback tenant",
                Status = TenantStatus.Active,
                BillingStatus = BillingStatus.Trial
            },
            feature,
            offering,
            version,
            new SaasOfferingVersionFeature { OfferingVersionId = version.Id, FeatureCode = feature.Code },
            quote,
            order,
            orderItem,
            payment
        ];
        return new CallbackFixture(tenantId, version.Id, order.Id, payment.Id, notification, entities);
    }

    private static async Task PublishVersionAsync(ApiTestFactory factory, Guid versionId)
    {
        using var scope = factory.CreateSystemScope("Publish callback plan fixture");
        var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var version = await db.SaasOfferingVersions.SingleAsync(value => value.Id == versionId);
        version.Status = SaasOfferingVersionStatus.Published;
        version.PublishedAt = DateTimeOffset.UtcNow;
        await db.SaveChangesAsync();
    }

    private static async Task AssertPaymentRemainsPendingAsync(ApiTestFactory factory, Guid paymentId)
    {
        using var scope = factory.CreateSystemScope("Verify rejected platform callback");
        var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Equal(PlatformBillingPaymentStatus.Pending,
            (await db.PlatformBillingPayments.AsNoTracking().SingleAsync(value => value.Id == paymentId)).Status);
        Assert.False(await db.PlatformBillingPaymentEvents.AnyAsync(value => value.PaymentId == paymentId));
    }

    private static async Task<string> ReadCodeAsync(HttpResponseMessage response)
    {
        using var payload = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        return payload.RootElement.GetProperty("code").GetString() ?? string.Empty;
    }

    private sealed record CallbackFixture(
        Guid TenantId,
        Guid VersionId,
        Guid OrderId,
        Guid PaymentId,
        PaymentNotificationResult Notification,
        object[] Entities);

    private sealed class FakePlatformBillingPaymentGateway(PaymentNotificationResult notification)
        : IPlatformBillingPaymentGateway
    {
        public List<string> ParsedProviders { get; } = [];

        public Task<CreatePaymentProviderResult> CreatePaymentAsync(
            string provider,
            CreatePaymentProviderRequest request,
            CancellationToken cancellationToken = default)
        {
            return Task.FromResult(new CreatePaymentProviderResult(
                provider,
                request.Method,
                "pending",
                null,
                JsonDefaults.Object(),
                JsonDefaults.Object()));
        }

        public Task<PaymentNotificationResult> ParseNotificationAsync(
            string provider,
            PaymentNotificationRequest request,
            CancellationToken cancellationToken = default)
        {
            ParsedProviders.Add(provider);
            return Task.FromResult(notification);
        }
    }
}