using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Api.Contracts;
using Tiku.Application.Security;
using Tiku.Domain.Commerce;
using Tiku.Domain.Common;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Auth;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class PlatformAdminEndpointTests
{
    [Fact]
    public async Task Platform_admin_can_manage_tenant_subscription_domain_recheck_and_audit()
    {
        await using var factory = new ApiTestFactory(configurationOverrides: new Dictionary<string, string?>
        {
            ["Tenancy:Resolution:PlatformHosts:0"] = "localhost"
        });
        var platform = await SeedPlatformAdminAsync(factory);
        var tenantId = Guid.NewGuid();
        var domainId = Guid.NewGuid();
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = "tenant-six-a",
                Name = "Tenant Six A",
                Status = TenantStatus.Active,
                BillingStatus = BillingStatus.Trial
            },
            new TenantDomain
            {
                Id = domainId,
                TenantId = tenantId,
                Host = "six-a.example.test",
                Status = TenantDomainStatus.Active,
                IsPrimary = true,
                VerificationToken = "verify-six-a",
                VerifiedAt = DateTimeOffset.UtcNow,
                DnsVerifiedAt = DateTimeOffset.UtcNow,
                TlsReadyAt = DateTimeOffset.UtcNow
            },
            new PlatformSaasPlan
            {
                Code = "standard",
                Name = "Standard",
                BaseAmountCents = 99900,
                Status = PlatformSaasPlanStatus.Active
            });

        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsPlatformAsync(platform.Email));

        var overview = await client.GetAsync("/api/platform-admin/overview");
        var tenants = await client.GetAsync("/api/platform-admin/tenants?search=six-a");
        var planModules = await client.PutAsJsonAsync(
            "/api/platform-admin/plans/standard/modules",
            new ReplacePlatformPlanModulesDto { ModuleCodes = ["content", "job", "settings"] });
        var subscription = await client.PostAsJsonAsync(
            "/api/platform-admin/subscriptions",
            new UpsertPlatformSubscriptionDto
            {
                TenantId = tenantId,
                PlanCode = "standard",
                Status = TenantSubscriptionStatus.Active,
                StartsAt = DateTimeOffset.UtcNow.AddDays(-1),
                ExpiresAt = DateTimeOffset.UtcNow.AddDays(30),
                AmountCents = 99900
            });
        var moduleOverride = await client.PutAsJsonAsync(
            $"/api/platform-admin/tenants/{tenantId}/module-overrides/content",
            new UpsertPlatformTenantModuleOverrideDto
            {
                Mode = TenantModuleOverrideMode.Disabled,
                Reason = "integration test capability revocation"
            });
        var recheck = await client.PostAsync($"/api/platform-admin/domains/{domainId}/recheck", null);
        var suspended = await client.PatchAsJsonAsync(
            "/api/platform-admin/tenants/status",
            new UpdatePlatformTenantStatusDto
            {
                TenantId = tenantId,
                Status = TenantStatus.Suspended,
                BillingStatus = BillingStatus.PastDue,
                Reason = "integration test suspension"
            });

        using var runtimeRequest = new HttpRequestMessage(HttpMethod.Get, "/api/runtime/bootstrap");
        runtimeRequest.Headers.Host = "six-a.example.test";
        var runtimeAfterSuspend = await client.SendAsync(runtimeRequest);

        Assert.Equal(HttpStatusCode.OK, overview.StatusCode);
        Assert.Equal(HttpStatusCode.OK, tenants.StatusCode);
        Assert.Equal(HttpStatusCode.OK, planModules.StatusCode);
        Assert.Equal(HttpStatusCode.OK, subscription.StatusCode);
        Assert.Equal(HttpStatusCode.OK, moduleOverride.StatusCode);
        Assert.Equal(HttpStatusCode.OK, recheck.StatusCode);
        Assert.Equal(HttpStatusCode.OK, suspended.StatusCode);
        Assert.Equal(HttpStatusCode.NotFound, runtimeAfterSuspend.StatusCode);

        using var scope = factory.CreateSystemScope("Verify platform admin side effects");
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.True(await dbContext.BackgroundJobs.AnyAsync(job =>
            job.TenantId == tenantId &&
            job.JobType == "tenant_domain_recheck"));
        Assert.True(await dbContext.AuditLogs.AnyAsync(log =>
            log.ActorUserId == platform.UserId &&
            log.Action == "platform.tenant.status_changed"));
        Assert.True(await dbContext.AuditLogs.AnyAsync(log =>
            log.ActorUserId == platform.UserId &&
            log.Action == "platform.plan.modules.replaced"));
        Assert.True(await dbContext.AuditLogs.AnyAsync(log =>
            log.ActorUserId == platform.UserId &&
            log.Action == "platform.tenant.module_override.updated"));
    }

    [Fact]
    public async Task Tenant_token_cannot_access_platform_admin_endpoints()
    {
        await using var factory = new ApiTestFactory();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var phone = "13866660000";
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Tenant Realm"
            },
            new User
            {
                Id = userId,
                Phone = phone,
                Name = "Tenant Admin"
            }.WithTestPassword(),
            new TenantMembership
            {
                TenantId = tenantId,
                UserId = userId,
                Role = TenantRole.TenantAdmin,
                Status = MembershipStatus.Active
            });
        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone));

        var response = await client.GetAsync("/api/platform-admin/overview");

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

    [Fact]
    public async Task Platform_admin_can_manage_dunning_channels_and_retry_events()
    {
        await using var factory = new ApiTestFactory(configurationOverrides: new Dictionary<string, string?>
        {
            ["Tenancy:Resolution:PlatformHosts:0"] = "localhost"
        });
        var platform = await SeedPlatformAdminAsync(factory);
        var tenantId = Guid.NewGuid();
        var invoiceId = Guid.NewGuid();
        var reminderId = Guid.NewGuid();
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = "tenant-dunning-a",
                Name = "Tenant Dunning A",
                Status = TenantStatus.Active,
                BillingStatus = BillingStatus.PastDue
            },
            new TenantInvoice
            {
                Id = invoiceId,
                TenantId = tenantId,
                InvoiceNo = "INV-DUNNING-1",
                Status = TenantInvoiceStatus.Overdue,
                TotalCents = 10_000,
                BalanceCents = 10_000
            },
            new TenantInvoiceReminder
            {
                Id = reminderId,
                TenantId = tenantId,
                InvoiceId = invoiceId,
                ReminderType = TenantInvoiceReminderType.Overdue,
                Channel = TenantInvoiceReminderChannel.Wechat,
                Status = TenantInvoiceReminderStatus.Failed,
                ReminderDate = DateOnly.FromDateTime(DateTime.UtcNow.Date),
                BalanceCentsSnapshot = 10_000
            });
        using var client = factory.CreateClient();
        client.UseAccessToken(await client.LoginAsPlatformAsync(platform.Email));

        var upsertResponse = await client.PutAsJsonAsync(
            "/api/platform-admin/dunning-notification-channels",
            new UpsertPlatformDunningChannelDto
            {
                ChannelCode = "wecom-overdue",
                Name = "企业微信逾期提醒",
                Provider = PlatformDunningProvider.Wecom,
                WebhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=secret-key",
                SecretRef = "platform_secrets:dunning:wecom:default",
                ReminderTypes = ["overdue"],
                ReminderChannels = ["wechat"],
                MinReminderLevel = 2,
                TenantIds = [tenantId]
            });
        var upsertBody = await upsertResponse.Content.ReadAsStringAsync();
        var channelJson = JsonDocument.Parse(upsertBody);
        var channelId = channelJson.RootElement.GetProperty("id").GetGuid();
        await factory.SeedAsync(new PlatformDunningNotificationEvent
        {
            TenantId = tenantId,
            ChannelId = channelId,
            ReminderId = reminderId,
            InvoiceId = invoiceId,
            Provider = PlatformDunningProvider.Wecom,
            Status = PlatformDunningNotificationStatus.Failed,
            Attempts = 2,
            LastError = "timeout",
            LastHttpCode = 500,
            LastResponseSummary = "server error",
            RequestPayload = JsonSerializer.SerializeToElement(new { phone = "13800001111", amount = 10000 })
        });

        var channelsResponse = await client.GetAsync("/api/platform-admin/dunning-notification-channels?search=wecom");
        var eventsResponse = await client.GetAsync("/api/platform-admin/dunning-notification-events?status=failed");
        var eventsJson = await JsonDocument.ParseAsync(await eventsResponse.Content.ReadAsStreamAsync());
        var eventId = eventsJson.RootElement.GetProperty("items")[0].GetProperty("id").GetGuid();
        var detailResponse = await client.GetAsync($"/api/platform-admin/dunning-notification-events/detail?eventId={eventId}");
        var retryResponse = await client.PostAsJsonAsync(
            "/api/platform-admin/dunning-notification-events/retry",
            new RetryPlatformDunningEventDto
            {
                EventId = eventId,
                Reason = "manual retry"
            });
        var disableResponse = await client.PostAsJsonAsync(
            "/api/platform-admin/dunning-notification-channels/disable",
            new DisablePlatformDunningChannelDto
            {
                ChannelId = channelId,
                Reason = "disable test"
            });

        Assert.Equal(HttpStatusCode.OK, upsertResponse.StatusCode);
        Assert.DoesNotContain("secret-key", upsertBody, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("https://qyapi.weixin.qq.com/****", upsertBody, StringComparison.OrdinalIgnoreCase);
        Assert.Equal(HttpStatusCode.OK, channelsResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, eventsResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, retryResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, disableResponse.StatusCode);

        using var scope = factory.CreateSystemScope("Verify platform dunning side effects");
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var storedEvent = await dbContext.PlatformDunningNotificationEvents.AsNoTracking().SingleAsync(item => item.Id == eventId);
        var storedChannel = await dbContext.PlatformDunningNotificationChannels.AsNoTracking().SingleAsync(item => item.Id == channelId);
        Assert.Equal(PlatformDunningNotificationStatus.Pending, storedEvent.Status);
        Assert.Null(storedEvent.LastError);
        Assert.False(storedChannel.Enabled);
        Assert.True(await dbContext.AuditLogs.AnyAsync(log =>
            log.ActorUserId == platform.UserId &&
            log.Action == "platform.dunning_event.retry_requested"));
        Assert.True(await dbContext.AuditLogs.AnyAsync(log =>
            log.ActorUserId == platform.UserId &&
            log.Action == "platform.dunning_channel.disabled"));
    }

    private static async Task<(Guid UserId, string Email)> SeedPlatformAdminAsync(ApiTestFactory factory)
    {
        var userId = Guid.NewGuid();
        var roleId = Guid.NewGuid();
        var email = $"platform-{Guid.NewGuid():N}@example.test";
        var permissions = BackendPermissions.Platform.Select(code => new BackendPermission
        {
            Code = code,
            Name = code,
            Area = BackendPermissionArea.Platform,
            Module = code.Split(':')[1],
            IsSystem = true
        }).Cast<object>().ToList();
        await factory.SeedAsync(
            [
                ..permissions,
                new User
                {
                    Id = userId,
                    Email = email,
                    NormalizedEmail = email.ToUpperInvariant(),
                    UserName = email,
                    NormalizedUserName = email.ToUpperInvariant(),
                    Name = "Platform Admin",
                    PrimaryRole = "platform_admin",
                    RawProfile = JsonDefaults.Object()
                }.WithTestPassword(),
                new PlatformBackendRole
                {
                    Id = roleId,
                    Code = "platform_super_admin",
                    Name = "Platform Super Admin",
                    Status = BackendRoleStatus.Active,
                    IsSystem = true
                },
                ..BackendPermissions.Platform.Select(code => new PlatformBackendRolePermission
                {
                    RoleId = roleId,
                    PermissionCode = code
                }),
                new PlatformBackendUserRole
                {
                    UserId = userId,
                    RoleId = roleId
                }
            ]);
        return (userId, email);
    }
}
