using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Contracts;
using Tiku.Application.Commerce;
using Tiku.Application.Security;
using Tiku.Domain.Commerce;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class CommerceEndpointTests
{
    [Fact]
    public async Task Anonymous_commerce_request_returns_401()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        using var client = factory.CreateClient();

        var response = await client.GetAsync("/api/student/commerce/orders");

        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task Non_member_cannot_create_order()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var sessionId = await factory.SeedActiveSessionAsync(userId, tenantId);
        var planId = Guid.NewGuid();
        await factory.SeedAsync(
            new SvipPlan
            {
                Id = planId,
                TenantId = tenantId,
                Name = "月卡",
                PriceCents = 999,
                Days = 39,
                IsActive = true
            });
        using var client = factory.CreateClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Bearer",
            TestJwtKeys.CreateToken([
                new Claim(TikuClaimTypes.UserId, userId.ToString()),
                new Claim(TikuClaimTypes.SessionId, sessionId.ToString()),
                new Claim(TikuClaimTypes.TenantId, tenantId.ToString())
            ]));

        var response = await client.PostAsJsonAsync(
            "/api/student/commerce/orders",
            new CreateCommerceOrderDto
            {
                PlanId = planId,
                Quantity = 1
            });

        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }

    [Fact]
    public async Task Student_can_create_and_query_svip_order()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var createResponse = await client.PostAsJsonAsync(
            "/api/student/commerce/orders",
            new CreateCommerceOrderDto
            {
                PlanId = seed.PlanId,
                Quantity = 2,
                PayMethod = "manual",
                PayProvider = "manual"
            });
        var created = await createResponse.Content.ReadFromJsonAsync<CommerceOrderItem>();
        var listResponse = await client.GetAsync("/api/student/commerce/orders?limit=5");
        var list = await listResponse.Content.ReadFromJsonAsync<CommerceOrderList>();
        var detailResponse = await client.GetAsync($"/api/student/commerce/orders/{created!.OrderNo}");

        Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
        Assert.Equal(1998, created.AmountCents);
        Assert.Equal(78, created.Days);
        Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
        Assert.Contains(list!.Items, item => item.OrderNo == created.OrderNo);
        Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
    }

    [Fact]
    public async Task Paid_provider_result_marks_order_paid_and_grants_entitlement()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway("paid"));
        var seed = await SeedLoginUserAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);
        var order = await CreateOrderAsync(client, seed.PlanId);

        var paymentResponse = await client.PostAsJsonAsync(
            "/api/student/commerce/payments",
            new CreateCommercePaymentDto
            {
                OrderNo = order.OrderNo,
                Provider = "manual",
                Method = "manual"
            });
        var payment = await paymentResponse.Content.ReadFromJsonAsync<CommercePaymentItem>();
        var entitlementResponse = await client.GetAsync("/api/student/commerce/entitlements/current");
        var entitlement = await entitlementResponse.Content.ReadFromJsonAsync<CurrentEntitlementItem>();
        var orderResponse = await client.GetAsync($"/api/student/commerce/orders/{order.OrderNo}");
        var paidOrder = await orderResponse.Content.ReadFromJsonAsync<CommerceOrderItem>();

        Assert.Equal(HttpStatusCode.OK, paymentResponse.StatusCode);
        Assert.Equal("Paid", payment!.Status);
        Assert.Equal("Paid", paidOrder!.Status);
        Assert.Equal(HttpStatusCode.OK, entitlementResponse.StatusCode);
        Assert.True(entitlement!.IsActive);
        Assert.Equal("svip", entitlement.EntitlementType);
    }

    [Fact]
    public async Task Pending_payment_can_be_requested_idempotently()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);
        var order = await CreateOrderAsync(client, seed.PlanId);
        var request = new CreateCommercePaymentDto
        {
            OrderNo = order.OrderNo,
            Provider = "manual",
            Method = "manual"
        };

        var first = await client.PostAsJsonAsync("/api/student/commerce/payments", request);
        var second = await client.PostAsJsonAsync("/api/student/commerce/payments", request);
        var firstPayment = await first.Content.ReadFromJsonAsync<CommercePaymentItem>();
        var secondPayment = await second.Content.ReadFromJsonAsync<CommercePaymentItem>();

        Assert.Equal(HttpStatusCode.OK, first.StatusCode);
        Assert.Equal(HttpStatusCode.OK, second.StatusCode);
        Assert.Equal(firstPayment!.Id, secondPayment!.Id);
    }

    [Fact]
    public async Task Student_can_claim_and_check_coupon()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        await factory.SeedAsync(new Coupon
        {
            TenantId = seed.TenantId,
            Code = "SAVE5",
            PlanId = seed.PlanId,
            DiscountType = DiscountType.Fixed,
            DiscountValue = 5,
            MaxUses = 10
        });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var claimResponse = await client.PostAsJsonAsync(
            "/api/student/commerce/coupons/claim",
            new ClaimCommerceCouponDto { CouponCode = "SAVE5" });
        var coupons = await (await client.GetAsync("/api/student/commerce/coupons"))
            .Content
            .ReadFromJsonAsync<CommerceCouponList>();
        var check = await (await client.PostAsJsonAsync(
                "/api/student/commerce/coupons/check",
                new CheckCommerceCouponDto
                {
                    CouponCode = "SAVE5",
                    PlanId = seed.PlanId,
                    Quantity = 1
                }))
            .Content
            .ReadFromJsonAsync<CommerceCouponCheckResult>();

        Assert.Equal(HttpStatusCode.OK, claimResponse.StatusCode);
        Assert.Contains(coupons!.Items, item => item.CouponCode == "SAVE5" && item.Status == "Claimed");
        Assert.True(check!.Valid);
        Assert.Equal(500, check.DiscountCents);
        Assert.Equal(499, check.PayableAmountCents);
    }

    [Fact]
    public async Task Coupon_can_make_order_zero_amount_and_grant_entitlement()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        await factory.SeedAsync(new Coupon
        {
            TenantId = seed.TenantId,
            Code = "FREE",
            PlanId = seed.PlanId,
            DiscountType = DiscountType.Fixed,
            DiscountValue = 9.99m,
            MaxUses = 10
        });
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);

        var response = await client.PostAsJsonAsync(
            "/api/student/commerce/orders",
            new CreateCommerceOrderDto
            {
                PlanId = seed.PlanId,
                Quantity = 1,
                CouponCode = "FREE"
            });
        var order = await response.Content.ReadFromJsonAsync<CommerceOrderItem>();
        var entitlement = await (await client.GetAsync("/api/student/commerce/entitlements/current"))
            .Content
            .ReadFromJsonAsync<CurrentEntitlementItem>();
        var coupons = await (await client.GetAsync("/api/student/commerce/coupons?status=used"))
            .Content
            .ReadFromJsonAsync<CommerceCouponList>();

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal(0, order!.AmountCents);
        Assert.Equal("Paid", order.Status);
        Assert.True(entitlement!.IsActive);
        Assert.Contains(coupons!.Items, item => item.CouponCode == "FREE" && item.DiscountPreviewCents == 999);
    }

    [Fact]
    public async Task Payment_notification_marks_order_paid_once()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);
        var order = await CreateOrderAsync(client, seed.PlanId);
        var request = new CreateCommercePaymentDto
        {
            OrderNo = order.OrderNo,
            Provider = "manual",
            Method = "manual"
        };
        await client.PostAsJsonAsync("/api/student/commerce/payments", request);

        client.DefaultRequestHeaders.Authorization = null;
        var payload = new
        {
            eventId = "notify-001",
            orderNo = order.OrderNo,
            providerTradeNo = "trade-notify-001",
            amountCents = order.AmountCents,
            paid = true,
            signatureValid = true
        };
        var firstNotify = await client.PostAsJsonAsync(
            $"/api/student/commerce/payments/notify/wechat-pay?tenantCode={seed.TenantId:N}",
            payload);
        var secondNotify = await client.PostAsJsonAsync(
            $"/api/student/commerce/payments/notify/wechat-pay?tenantCode={seed.TenantId:N}",
            payload);
        await LoginAsync(client, seed);
        var entitlement = await (await client.GetAsync("/api/student/commerce/entitlements/current"))
            .Content
            .ReadFromJsonAsync<CurrentEntitlementItem>();
        using var scope = factory.CreateSystemScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var events = dbContext.PaymentEvents.Count(item => item.EventId == "notify-001");

        Assert.Equal(HttpStatusCode.OK, firstNotify.StatusCode);
        Assert.Equal(HttpStatusCode.OK, secondNotify.StatusCode);
        Assert.True(entitlement!.IsActive);
        Assert.Equal(1, events);
    }

    [Fact]
    public async Task Invalid_payment_notification_signature_does_not_pay_order()
    {
        await using var factory = new ApiTestFactory(paymentProviderGateway: new FakePaymentGateway());
        var seed = await SeedLoginUserAsync(factory);
        using var client = factory.CreateClient();
        await LoginAsync(client, seed);
        var order = await CreateOrderAsync(client, seed.PlanId);

        client.DefaultRequestHeaders.Authorization = null;
        var notifyResponse = await client.PostAsJsonAsync(
            $"/api/student/commerce/payments/notify/wechat-pay?tenantCode={seed.TenantId:N}",
            new
            {
                eventId = "notify-invalid",
                orderNo = order.OrderNo,
                providerTradeNo = "trade-invalid",
                amountCents = order.AmountCents,
                paid = true,
                signatureValid = false
            });
        await LoginAsync(client, seed);
        var orderResponse = await client.GetAsync($"/api/student/commerce/orders/{order.OrderNo}");
        var currentOrder = await orderResponse.Content.ReadFromJsonAsync<CommerceOrderItem>();

        Assert.Equal(HttpStatusCode.BadRequest, notifyResponse.StatusCode);
        Assert.Equal("Pending", currentOrder!.Status);
    }

    private static async Task<CommerceOrderItem> CreateOrderAsync(HttpClient client, Guid planId)
    {
        var response = await client.PostAsJsonAsync(
            "/api/student/commerce/orders",
            new CreateCommerceOrderDto
            {
                PlanId = planId,
                Quantity = 1,
                PayMethod = "manual",
                PayProvider = "manual"
            });
        response.EnsureSuccessStatusCode();
        return (await response.Content.ReadFromJsonAsync<CommerceOrderItem>())!;
    }

    private static async Task<LoginSeed> SeedLoginUserAsync(
        ApiTestFactory factory,
        bool includeMembership = true)
    {
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var planId = Guid.NewGuid();
        var phone = "13800000000";
        var entities = new List<object>
        {
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Commerce Tenant"
            },
            new User
            {
                Id = userId,
                Phone = phone,
                Name = "Commerce User"
            }.WithTestPassword(),
            new SvipPlan
            {
                Id = planId,
                TenantId = tenantId,
                Name = "月卡",
                PriceCents = 999,
                Days = 39,
                IsActive = true
            },
            new TenantExternalProvider
            {
                TenantId = tenantId,
                Capability = TenantExternalProviderCapability.Payment,
                Provider = "manual",
                Status = TenantExternalProviderStatus.Active,
                ConfigPublic = JsonSerializer.SerializeToElement(new { mode = "TenantCollect" })
            }
        };
        if (includeMembership)
            entities.Add(new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.Student,
                Status = MembershipStatus.Active
            });

        await factory.SeedAsync(entities.ToArray());
        return new LoginSeed(tenantId, userId, planId, phone);
    }

    private static async Task LoginAsync(HttpClient client, LoginSeed seed)
    {
        client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));
    }

    private sealed record LoginSeed(Guid TenantId, Guid UserId, Guid PlanId, string Phone);

    private sealed class FakePaymentGateway(string status = "pending") : IPaymentProviderGateway
    {
        public Task<CreatePaymentProviderResult> CreatePaymentAsync(
            string provider,
            CreatePaymentProviderRequest request,
            CancellationToken cancellationToken = default)
        {
            var payload = JsonSerializer.SerializeToElement(new
            {
                request.OrderNo,
                request.AmountCents,
                status
            });
            return Task.FromResult(new CreatePaymentProviderResult(
                provider,
                request.Method,
                status,
                status == "paid" ? $"trade-{request.OrderNo}" : null,
                payload,
                payload));
        }

        public Task<PaymentNotificationResult> ParsePaymentNotificationAsync(
            string provider,
            PaymentNotificationRequest request,
            CancellationToken cancellationToken = default)
        {
            var body = request.Body;
            var eventId = GetString(body, "eventId") ?? Guid.NewGuid().ToString("N");
            var orderNo = GetString(body, "orderNo") ?? throw new InvalidOperationException("orderNo missing");
            var tradeNo = GetString(body, "providerTradeNo");
            var amount = GetInt(body, "amountCents");
            var paid = GetBoolean(body, "paid");
            var signatureValid = GetBoolean(body, "signatureValid");
            return Task.FromResult(new PaymentNotificationResult(
                provider,
                "payment_notify",
                eventId,
                orderNo,
                tradeNo,
                amount,
                paid,
                signatureValid,
                DateTimeOffset.UtcNow,
                body));
        }

        private static string? GetString(JsonElement element, string key)
        {
            return element.ValueKind == JsonValueKind.Object &&
                   element.TryGetProperty(key, out var property) &&
                   property.ValueKind == JsonValueKind.String
                ? property.GetString()
                : null;
        }

        private static int GetInt(JsonElement element, string key)
        {
            return element.ValueKind == JsonValueKind.Object &&
                   element.TryGetProperty(key, out var property) &&
                   property.TryGetInt32(out var value)
                ? value
                : 0;
        }

        private static bool GetBoolean(JsonElement element, string key)
        {
            return element.ValueKind == JsonValueKind.Object &&
                   element.TryGetProperty(key, out var property) &&
                   property.ValueKind == JsonValueKind.True;
        }
    }
}