using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.PlatformBilling;
using Tiku.Application.Security;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.PlatformBilling;

internal sealed class PlatformBillingAdminService(
    ITenantExecutionScope tenantExecutionScope) : IPlatformBillingAdminService
{
    public Task<IReadOnlyCollection<PlatformBillingOrder>> GetOrdersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<PlatformBillingOrder>>("list SaaS orders", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.PlatformBillingOrders.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<PlatformBillingOrderStatus>(query.Status));
            return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<PlatformBillingPayment>> GetPaymentsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<PlatformBillingPayment>>("list SaaS payments", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.PlatformBillingPayments.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<PlatformBillingPaymentStatus>(query.Status));
            return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<PlatformBillingRefund>> GetRefundsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<PlatformBillingRefund>>("list SaaS refunds", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.PlatformBillingRefunds.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<PlatformBillingRefundStatus>(query.Status));
            return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<PlatformBillingInvoice>> GetInvoicesAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<PlatformBillingInvoice>>("list SaaS invoices", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.PlatformBillingInvoices.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<PlatformBillingInvoiceStatus>(query.Status));
            return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<TenantFeatureUsage>> GetUsageAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<TenantFeatureUsage>>("list SaaS usage", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.TenantFeatureUsages.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            return await values.OrderByDescending(value => value.PeriodStart).ThenBy(value => value.MetricCode).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<PlatformBillingInvoiceReminder>> GetInvoiceRemindersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<PlatformBillingInvoiceReminder>>("list SaaS invoice reminders", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.PlatformBillingInvoiceReminders.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<PlatformBillingInvoiceReminderStatus>(query.Status));
            return await values.OrderByDescending(value => value.ReminderDate).ThenByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<IReadOnlyCollection<TenantSaasSubscription>> GetSubscriptionsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) =>
        ExecuteAsync<IReadOnlyCollection<TenantSaasSubscription>>("list SaaS subscriptions", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var values = db.TenantSaasSubscriptions.AsNoTracking();
            if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId);
            if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse<TenantSaasSubscriptionStatus>(query.Status));
            return await values.OrderByDescending(value => value.UpdatedAt).Take(Limit(query.Limit)).ToArrayAsync(token);
        }, cancellationToken);

    public Task<PlatformBillingPayment> ConfirmManualPaymentAsync(
        SaasCatalogActor actor,
        ConfirmManualPaymentCommand command,
        CancellationToken cancellationToken = default) =>
        ExecuteAsync("confirm manual SaaS payment", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var payment = await db.PlatformBillingPayments.AsNoTracking().SingleOrDefaultAsync(value => value.Id == command.PaymentId, token)
                ?? throw Error("Platform billing payment was not found.", "platform_billing_payment_not_found");
            if (payment.Provider != "manual")
            {
                throw Error("Only a manual payment can be confirmed by an operator.", "platform_billing_manual_payment_required");
            }
            if (string.IsNullOrWhiteSpace(command.Reason))
            {
                throw Error("Manual payment confirmation reason is required.", "platform_billing_reason_required");
            }
            return await services.GetRequiredService<IPlatformBillingSettlementService>().MarkPaidAsync(
                payment.Id,
                $"manual-{payment.Id:N}",
                "manual_payment_confirmed",
                command.ProviderTradeNo,
                command.PaidAt ?? DateTimeOffset.UtcNow,
                JsonSerializer.SerializeToElement(new { command.Reason }),
                actor.UserId,
                token);
        }, cancellationToken);

    public Task<TenantFeatureOverride> UpsertFeatureOverrideAsync(
        SaasCatalogActor actor,
        UpsertTenantFeatureOverrideCommand command,
        CancellationToken cancellationToken = default) =>
        ExecuteAsync("upsert tenant feature override", async (services, token) =>
        {
            var db = services.GetRequiredService<TikuDbContext>();
            var featureCode = command.FeatureCode.Trim().ToLowerInvariant();
            if (string.IsNullOrWhiteSpace(command.Reason)) throw Error("Override reason is required.", "platform_billing_reason_required");
            if (!await db.Tenants.AnyAsync(value => value.Id == command.TenantId, token)) throw Error("Tenant was not found.", "tenant_not_found");
            if (!await db.SaasFeatures.AnyAsync(value => value.Code == featureCode && !value.IsCore, token)) throw Error("SaaS feature was not found.", "saas_feature_not_found");
            var item = await db.TenantFeatureOverrides.SingleOrDefaultAsync(value => value.TenantId == command.TenantId && value.FeatureCode == featureCode, token);
            if (item is null)
            {
                item = new TenantFeatureOverride { TenantId = command.TenantId, FeatureCode = featureCode };
                db.TenantFeatureOverrides.Add(item);
            }
            item.Mode = command.Mode;
            item.ExpiresAt = command.ExpiresAt;
            item.Reason = command.Reason.Trim();
            db.AuditLogs.Add(new AuditLog
            {
                TenantId = command.TenantId,
                ActorUserId = actor.UserId,
                Action = "platform.saas.feature_override.updated",
                TargetType = "tenant_feature_overrides",
                TargetId = item.Id.ToString(),
                Details = JsonSerializer.SerializeToElement(new { item.FeatureCode, item.Mode, item.ExpiresAt, item.Reason })
            });
            await db.SaveChangesAsync(token);
            await services.GetRequiredService<ITenantFeatureCacheInvalidator>()
                .InvalidateAsync(command.TenantId, token);
            return item;
        }, cancellationToken);

    private Task<TResult> ExecuteAsync<TResult>(
        string reason,
        Func<IServiceProvider, CancellationToken, Task<TResult>> operation,
        CancellationToken cancellationToken) =>
        tenantExecutionScope.ExecuteAsync(
            new SystemScopeRequest(null, SystemScopeCallerType.Platform, nameof(PlatformBillingAdminService), reason, Guid.NewGuid().ToString("N"), true),
            operation,
            cancellationToken);

    private static T Parse<T>(string value) where T : struct, Enum =>
        Enum.TryParse<T>(value, true, out var parsed) ? parsed : throw Error("Status is invalid.", "status_invalid");
    private static int Limit(int value) => Math.Clamp(value, 1, 200);
    private static PlatformBillingException Error(string message, string code) => new(message, code);
}
