using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Npgsql;
using Tiku.Application.Security;
using Tiku.Domain.Growth;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.Bootstrap;

public static class DevelopmentPlatformAdminSeeder
{
    public const string Email = "admin@tiku.local";
    public const string RoleCode = PlatformAdminBootstrapper.SuperAdminRoleCode;

    public static void Configure(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSeeding((context, _) => Seed((TikuDbContext)context))
            .UseAsyncSeeding((context, _, cancellationToken) =>
                SeedAsync((TikuDbContext)context, cancellationToken));
    }

    public static bool Seed(TikuDbContext dbContext)
    {
        ReloadPostgresTypes(dbContext);

        if (dbContext.PlatformBackendUserRoles.Any())
        {
            EnsurePlatformPermissionCatalog(dbContext);
            SeedDemoTenantCapabilities(dbContext);
            dbContext.SaveChanges();
            return false;
        }

        var temporaryPassword = GenerateTemporaryPassword();
        EnsureEmailIsAvailable(dbContext.Users.Any(user =>
            user.NormalizedEmail == Email.ToUpperInvariant() ||
            user.NormalizedUserName == Email.ToUpperInvariant()));
        var permissionCodes = BackendPermissions.Platform.ToArray();
        var existingPermissionCodes = dbContext.BackendPermissions
            .Where(permission => permissionCodes.Contains(permission.Code))
            .Select(permission => permission.Code)
            .ToHashSet(StringComparer.Ordinal);
        AddSeedGraph(dbContext, temporaryPassword, permissionCodes, existingPermissionCodes);
        SeedDemoTenantCapabilities(dbContext);
        dbContext.SaveChanges();
        WriteFirstLoginInstructions(temporaryPassword);
        return true;
    }

    public static async Task<bool> SeedAsync(
        TikuDbContext dbContext,
        CancellationToken cancellationToken = default)
    {
        await ReloadPostgresTypesAsync(dbContext, cancellationToken);

        if (await dbContext.PlatformBackendUserRoles.AnyAsync(cancellationToken))
        {
            EnsurePlatformPermissionCatalog(dbContext);
            SeedDemoTenantCapabilities(dbContext);
            await dbContext.SaveChangesAsync(cancellationToken);
            return false;
        }

        var temporaryPassword = GenerateTemporaryPassword();
        var normalizedEmail = Email.ToUpperInvariant();
        EnsureEmailIsAvailable(await dbContext.Users.AnyAsync(user =>
            user.NormalizedEmail == normalizedEmail ||
            user.NormalizedUserName == normalizedEmail, cancellationToken));
        var permissionCodes = BackendPermissions.Platform.ToArray();
        var existingPermissionCodes = (await dbContext.BackendPermissions
                .Where(permission => permissionCodes.Contains(permission.Code))
                .Select(permission => permission.Code)
                .ToArrayAsync(cancellationToken))
            .ToHashSet(StringComparer.Ordinal);
        AddSeedGraph(dbContext, temporaryPassword, permissionCodes, existingPermissionCodes);
        SeedDemoTenantCapabilities(dbContext);
        await dbContext.SaveChangesAsync(cancellationToken);
        WriteFirstLoginInstructions(temporaryPassword);
        return true;
    }

    private static void AddSeedGraph(
        TikuDbContext dbContext,
        string temporaryPassword,
        IReadOnlyCollection<string> permissionCodes,
        IReadOnlySet<string> existingPermissionCodes)
    {
        var normalizedEmail = Email.ToUpperInvariant();
        var user = new User
        {
            Email = Email,
            NormalizedEmail = normalizedEmail,
            UserName = Email,
            NormalizedUserName = normalizedEmail,
            Name = "Local Platform Administrator",
            EmailConfirmed = true,
            Status = UserStatus.Active,
            ForcePasswordChange = true
        };
        var passwordHasher = new PasswordHasher<User>(Options.Create(new PasswordHasherOptions
        {
            IterationCount = 210_000
        }));
        user.PasswordHash = passwordHasher.HashPassword(user, temporaryPassword);

        var role = new PlatformBackendRole
        {
            Code = RoleCode,
            Name = "Platform Super Administrator",
            Description = "Built-in Development administrator created by EF Core data seeding.",
            Status = BackendRoleStatus.Active,
            IsSystem = true
        };
        dbContext.Users.Add(user);
        dbContext.PlatformBackendRoles.Add(role);
        var platformModuleCodes = permissionCodes
            .Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
            .Distinct(StringComparer.Ordinal)
            .ToArray();
        var existingModuleCodes = dbContext.PermissionModules
            .Where(module => platformModuleCodes.Contains(module.Code))
            .Select(module => module.Code)
            .ToArray();
        dbContext.PermissionModules.AddRange(platformModuleCodes
            .Except(existingModuleCodes, StringComparer.Ordinal)
            .Select(code => new PermissionModule
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Platform
            }));
        dbContext.BackendPermissions.AddRange(
            permissionCodes
                .Where(code => !existingPermissionCodes.Contains(code))
                .Select(code => new BackendPermission
                {
                    Code = code,
                    Name = code,
                    Area = BackendPermissionArea.Platform,
                    PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code),
                    Description = "Built-in platform permission.",
                    IsSystem = true
                }));
        dbContext.PlatformBackendRolePermissions.AddRange(
            permissionCodes.Select(code => new PlatformBackendRolePermission
            {
                RoleId = role.Id,
                PermissionCode = code
            }));
        dbContext.PlatformBackendUserRoles.Add(new PlatformBackendUserRole
        {
            UserId = user.Id,
            RoleId = role.Id
        });
        dbContext.AuditLogs.Add(new AuditLog
        {
            ActorUserId = user.Id,
            Action = "platform.bootstrap_admin.created",
            TargetType = "users",
            TargetId = user.Id.ToString(),
            Details = JsonSerializer.SerializeToElement(new
            {
                user.Email,
                RoleCode,
                ForcePasswordChange = true,
                Source = "ef_core_use_seeding"
            })
        });
    }

    private static void EnsurePlatformPermissionCatalog(TikuDbContext dbContext)
    {
        var permissionCodes = BackendPermissions.Platform.ToArray();
        var moduleCodes = permissionCodes
            .Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
            .Distinct(StringComparer.Ordinal)
            .ToArray();
        var existingModuleCodes = dbContext.PermissionModules
            .Where(module => moduleCodes.Contains(module.Code))
            .Select(module => module.Code)
            .ToHashSet(StringComparer.Ordinal);
        foreach (var code in moduleCodes.Where(code => !existingModuleCodes.Contains(code)))
            dbContext.PermissionModules.Add(new PermissionModule
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Platform,
                RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code]
            });

        var existingPermissionCodes = dbContext.BackendPermissions
            .Where(permission => permissionCodes.Contains(permission.Code))
            .Select(permission => permission.Code)
            .ToHashSet(StringComparer.Ordinal);
        foreach (var code in permissionCodes.Where(code => !existingPermissionCodes.Contains(code)))
            dbContext.BackendPermissions.Add(new BackendPermission
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Platform,
                PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code),
                Description = "Built-in platform permission.",
                IsSystem = true
            });

        var superAdminRoleId = dbContext.PlatformBackendRoles
            .Where(role => role.Code == RoleCode)
            .Select(role => (Guid?)role.Id)
            .FirstOrDefault();
        if (superAdminRoleId is null) return;

        var boundPermissionCodes = dbContext.PlatformBackendRolePermissions
            .Where(binding => binding.RoleId == superAdminRoleId.Value)
            .Select(binding => binding.PermissionCode)
            .ToHashSet(StringComparer.Ordinal);
        dbContext.PlatformBackendRolePermissions.AddRange(permissionCodes
            .Where(code => !boundPermissionCodes.Contains(code))
            .Select(code => new PlatformBackendRolePermission
            {
                RoleId = superAdminRoleId.Value,
                PermissionCode = code
            }));
    }

    private static void SeedDemoTenantCapabilities(TikuDbContext dbContext)
    {
        var tenantA = EnsureTenant(dbContext, "demo-crm-school", "演示题库机构 A");
        var tenantB = EnsureTenant(dbContext, "demo-sms-campus", "演示题库机构 B");
        EnsureCrmDemo(dbContext, tenantA);
        EnsureSmsDemo(dbContext, tenantA, "aliyun", "登录验证码");
        EnsureSmsDemo(dbContext, tenantB, "tencent", "营销通知");
        EnsurePlatformPaymentDemo(dbContext, tenantA);
        EnsureTenantPaymentDemo(dbContext, tenantA);
    }

    private static Tenant EnsureTenant(TikuDbContext dbContext, string slug, string name)
    {
        var tenant = dbContext.Tenants.SingleOrDefault(value => value.Slug == slug);
        if (tenant is not null) return tenant;

        tenant = new Tenant
        {
            Slug = slug,
            Name = name,
            LegalName = $"{name}有限公司",
            Status = TenantStatus.Active,
            BillingStatus = BillingStatus.Active,
            Metadata = JsonSerializer.SerializeToElement(new { demo = true })
        };
        dbContext.Tenants.Add(tenant);
        dbContext.AuthorizationScopeVersions.Add(new AuthorizationScopeVersion
        {
            Realm = AuthRealm.Tenant,
            TenantId = tenant.Id
        });
        return tenant;
    }

    private static void EnsureCrmDemo(TikuDbContext dbContext, Tenant tenant)
    {
        if (!dbContext.CrmConfigs.Any(value => value.TenantId == tenant.Id))
            dbContext.CrmConfigs.Add(new CrmConfig
            {
                TenantId = tenant.Id,
                Enabled = true,
                Url = "https://crm.example.test/webhook/tiku",
                SecretRef = "tenant_secret:crm:demo:redacted",
                FormName = "学生留资表单",
                ExamType = "ielts",
                TimeoutSeconds = 10,
                DelaySeconds = 0,
                AssignmentMode = ReferralAssignmentMode.RoundRobin,
                AssignmentPool = JsonSerializer.SerializeToElement(new[] { "顾问一", "顾问二" }),
                AssignmentConfig = JsonSerializer.SerializeToElement(new { retry = 3, source = "development_seed" })
            });

        if (!dbContext.CrmWebhookQueue.Any(value =>
                value.TenantId == tenant.Id && value.IdempotencyKey == "demo-crm-failed-lead"))
            dbContext.CrmWebhookQueue.Add(new CrmWebhookQueueItem
            {
                TenantId = tenant.Id,
                RecordId = "CRM-DEMO-LEAD-001",
                LeadId = "LEAD-001",
                Source = "tenant.student.crm_push",
                Provider = "generic",
                Status = CrmWebhookQueueStatus.Failed,
                Attempts = 2,
                LastError = "demo timeout",
                LastHttpCode = 504,
                TargetUrl = "https://crm.example.test/webhook/tiku",
                IdempotencyKey = "demo-crm-failed-lead",
                Payload = JsonSerializer.SerializeToElement(new { student = "王同学", phoneMasked = "138****0001" })
            });
    }

    private static void EnsureSmsDemo(TikuDbContext dbContext, Tenant tenant, string provider, string templateName)
    {
        var scene = templateName.Contains("登录", StringComparison.Ordinal) ? "login" : "marketing";
        var channel = dbContext.SmsChannels.SingleOrDefault(value =>
            value.TenantId == tenant.Id && value.Provider == provider && value.Scene == scene);
        if (channel is null)
        {
            channel = new SmsChannel
            {
                TenantId = tenant.Id,
                Provider = provider,
                Name = $"{tenant.Name} {provider} 短信渠道",
                Signature = "题库演示",
                Scene = scene,
                Status = TenantExternalProviderStatus.Active,
                SecretRef = $"tenant_secret:sms:{provider}:redacted",
                MonthlyQuota = 10000,
                ConfigPublic = JsonSerializer.SerializeToElement(new { region = "cn", redacted = true }),
                Metadata = JsonSerializer.SerializeToElement(new { demo = true })
            };
            dbContext.SmsChannels.Add(channel);
        }

        var templateCode = $"demo_{provider}_{scene}";
        var template =
            dbContext.SmsTemplates.SingleOrDefault(value => value.TenantId == tenant.Id && value.Code == templateCode);
        if (template is null)
        {
            template = new SmsTemplate
            {
                TenantId = tenant.Id,
                ChannelId = channel.Id,
                Code = templateCode,
                Name = templateName,
                Type = scene == "login" ? SmsTemplateType.VerificationCode : SmsTemplateType.Marketing,
                AuditStatus = SmsTemplateAuditStatus.Approved,
                Status = SmsTemplateStatus.Active,
                ProviderTemplateCode = $"TPL_{provider.ToUpperInvariant()}_DEMO",
                Content = "您的验证码为 ${code}，请勿泄露。",
                ApprovedAt = DateTimeOffset.UtcNow,
                Metadata = JsonSerializer.SerializeToElement(new { demo = true })
            };
            dbContext.SmsTemplates.Add(template);
        }

        if (!dbContext.SmsSendLogs.Any(value =>
                value.TenantId == tenant.Id && value.ProviderMessageId == $"demo-{provider}-{scene}-001"))
            dbContext.SmsSendLogs.Add(new SmsSendLog
            {
                TenantId = tenant.Id,
                ChannelId = channel.Id,
                TemplateId = template.Id,
                Provider = provider,
                Scene = scene,
                PhoneMasked = "138****0001",
                Status = SmsSendLogStatus.Sent,
                ProviderMessageId = $"demo-{provider}-{scene}-001",
                SentAt = DateTimeOffset.UtcNow,
                Metadata = JsonSerializer.SerializeToElement(new { demo = true })
            });
    }

    private static void EnsurePlatformPaymentDemo(TikuDbContext dbContext, Tenant tenant)
    {
        var app = dbContext.PlatformPaymentApps.SingleOrDefault(value => value.AppCode == "platform_saas_collect");
        if (app is null)
        {
            app = new PlatformPaymentApp
            {
                AppCode = "platform_saas_collect",
                AppName = "平台 SaaS 收款",
                Status = PlatformPaymentAppStatus.Active,
                SettlementMode = "PlatformCollect",
                Description = "Development demo platform payment app.",
                Metadata = JsonSerializer.SerializeToElement(new { demo = true })
            };
            dbContext.PlatformPaymentApps.Add(app);
        }

        if (!dbContext.PlatformPaymentChannels.Any(value => value.AppId == app.Id && value.Provider == "manual"))
            dbContext.PlatformPaymentChannels.Add(new PlatformPaymentChannel
            {
                AppId = app.Id,
                Provider = "manual",
                Mode = "PlatformCollect",
                Status = PlatformPaymentChannelStatus.Active,
                DisplayName = "线下人工确认",
                SecretRef = "platform_payment:manual:redacted",
                CallbackPath = "/api/platform-billing/payments/notify/manual",
                ConfigPublic = JsonSerializer.SerializeToElement(new { manual = true })
            });

        if (!dbContext.PlatformBillingPayments.Any(value => value.PaymentNo == "PB-DEMO-MANUAL-001"))
        {
            var quote = new PlatformBillingQuote
            {
                TenantId = tenant.Id,
                QuoteNo = "BQ-DEMO-001",
                IdempotencyKey = "demo-platform-payment-quote",
                Status = PlatformBillingQuoteStatus.Converted,
                Purpose = PlatformBillingOrderPurpose.NewSubscription,
                OriginalAmountCents = 19900,
                TotalAmountCents = 19900,
                ExpiresAt = DateTimeOffset.UtcNow.AddDays(7)
            };
            var order = new PlatformBillingOrder
            {
                TenantId = tenant.Id,
                QuoteId = quote.Id,
                OrderNo = "BO-DEMO-001",
                IdempotencyKey = "demo-platform-payment-order",
                Purpose = PlatformBillingOrderPurpose.NewSubscription,
                Status = PlatformBillingOrderStatus.PendingPayment,
                OriginalAmountCents = 19900,
                TotalAmountCents = 19900,
                ExpiresAt = DateTimeOffset.UtcNow.AddDays(7)
            };
            var payment = new PlatformBillingPayment
            {
                TenantId = tenant.Id,
                OrderId = order.Id,
                PaymentNo = "PB-DEMO-MANUAL-001",
                IdempotencyKey = "demo-platform-payment-payment",
                Provider = "manual",
                Method = "bank_transfer",
                Status = PlatformBillingPaymentStatus.Pending,
                AmountCents = 19900
            };
            dbContext.PlatformBillingQuotes.Add(quote);
            dbContext.PlatformBillingOrders.Add(order);
            dbContext.PlatformBillingPayments.Add(payment);
            dbContext.PlatformBillingPaymentEvents.Add(new PlatformBillingPaymentEvent
            {
                TenantId = tenant.Id,
                PaymentId = payment.Id,
                Provider = "manual",
                ProviderEventId = "demo-platform-payment-created",
                EventType = "payment_created",
                Payload = JsonSerializer.SerializeToElement(new { demo = true, amountCents = 19900 })
            });
        }
    }

    private static void EnsureTenantPaymentDemo(TikuDbContext dbContext, Tenant tenant)
    {
        if (!dbContext.TenantExternalProviders.Any(value =>
                value.TenantId == tenant.Id && value.Capability == TenantExternalProviderCapability.Payment &&
                value.Provider == "manual"))
            dbContext.TenantExternalProviders.Add(new TenantExternalProvider
            {
                TenantId = tenant.Id,
                Capability = TenantExternalProviderCapability.Payment,
                Provider = "manual",
                Status = TenantExternalProviderStatus.Active,
                DisplayName = "租户线下收款",
                SecretRef = "tenant_payment:manual:redacted",
                ConfigPublic = JsonSerializer.SerializeToElement(new { manual = true }),
                Metadata = JsonSerializer.SerializeToElement(new { demo = true })
            });
    }

    private static string GenerateTemporaryPassword()
    {
        return $"Tiku!{Convert.ToHexString(RandomNumberGenerator.GetBytes(16))}9a";
    }

    private static void ReloadPostgresTypes(TikuDbContext dbContext)
    {
        if (dbContext.Database.IsNpgsql()) ((NpgsqlConnection)dbContext.Database.GetDbConnection()).ReloadTypes();
    }

    private static async Task ReloadPostgresTypesAsync(
        TikuDbContext dbContext,
        CancellationToken cancellationToken)
    {
        if (dbContext.Database.IsNpgsql())
            await ((NpgsqlConnection)dbContext.Database.GetDbConnection())
                .ReloadTypesAsync(cancellationToken);
    }

    private static void EnsureEmailIsAvailable(bool isAssigned)
    {
        if (isAssigned)
            throw new InvalidOperationException(
                $"Cannot create the Development platform administrator because '{Email}' is already assigned.");
    }

    private static void WriteFirstLoginInstructions(string temporaryPassword)
    {
        Console.WriteLine("Development platform administrator created by EF Core data seeding.");
        Console.WriteLine($"  Account: {Email}");
        Console.WriteLine($"  Temporary password: {temporaryPassword}");
        Console.WriteLine("  Change the temporary password at first sign-in. This password is shown only once.");
    }
}