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

namespace Tiku.Infrastructure.PlatformBilling;

internal sealed class SaasSubscriptionLifecycleService(
    TikuDbContext directoryDbContext,
    ITenantContext tenantContext,
    ITenantExecutionScope tenantExecutionScope,
    IOptions<SaasSubscriptionLifecycleOptions> options) : ISaasSubscriptionLifecycleService
{
    private readonly SaasSubscriptionLifecycleOptions options = options.Value;

    public async Task<int> ProcessDueAsync(
        DateTimeOffset? asOf = null,
        CancellationToken cancellationToken = default)
    {
        if (!options.Enabled) return 0;
        if (!tenantContext.IsSystem || tenantContext.TenantId.HasValue)
            throw new InvalidOperationException(
                "SaaS subscription lifecycle discovery requires a global system context.");

        var effectiveAt = asOf ?? DateTimeOffset.UtcNow;
        var tenantIds = await directoryDbContext.TenantSaasSubscriptions.AsNoTracking()
            .Where(subscription =>
                subscription.CurrentPeriodEnd <= effectiveAt &&
                (subscription.Status == TenantSaasSubscriptionStatus.Trial ||
                 subscription.Status == TenantSaasSubscriptionStatus.Active ||
                 subscription.Status == TenantSaasSubscriptionStatus.PastDue))
            .OrderBy(subscription => subscription.CurrentPeriodEnd)
            .ThenBy(subscription => subscription.TenantId)
            .Select(subscription => subscription.TenantId)
            .Distinct()
            .Take(Math.Clamp(options.BatchSize, 1, 1000))
            .ToArrayAsync(cancellationToken);

        var changed = 0;
        foreach (var tenantId in tenantIds)
        {
            var wasChanged = await tenantExecutionScope.ExecuteAsync(
                new SystemScopeRequest(
                    tenantId,
                    SystemScopeCallerType.Worker,
                    nameof(SaasSubscriptionLifecycleService),
                    "Apply due SaaS subscription lifecycle transition",
                    $"saas-subscription-lifecycle-{tenantId:N}-{effectiveAt:yyyyMMddHHmmss}"),
                (provider, token) => ProcessTenantAsync(provider, tenantId, effectiveAt, token),
                cancellationToken);
            if (wasChanged) changed++;
        }

        return changed;
    }

    private async Task<bool> ProcessTenantAsync(
        IServiceProvider services,
        Guid tenantId,
        DateTimeOffset asOf,
        CancellationToken cancellationToken)
    {
        var dbContext = services.GetRequiredService<TikuDbContext>();
        var subscription = await dbContext.TenantSaasSubscriptions
            .OrderByDescending(value => value.UpdatedAt)
            .FirstOrDefaultAsync(value =>
                    value.TenantId == tenantId &&
                    value.CurrentPeriodEnd <= asOf &&
                    (value.Status == TenantSaasSubscriptionStatus.Trial ||
                     value.Status == TenantSaasSubscriptionStatus.Active ||
                     value.Status == TenantSaasSubscriptionStatus.PastDue),
                cancellationToken);
        if (subscription is null) return false;

        var previousStatus = subscription.Status;
        var previousBaseVersionId = subscription.BaseOfferingVersionId;
        var items = await dbContext.TenantSaasSubscriptionItems
            .Where(value => value.TenantId == tenantId && value.SubscriptionId == subscription.Id)
            .ToArrayAsync(cancellationToken);
        var tenant = await dbContext.Tenants.SingleAsync(value => value.Id == tenantId, cancellationToken);

        string transition;
        if (subscription.CancelAtPeriodEnd)
        {
            transition = "cancelled_at_period_end";
            subscription.Status = TenantSaasSubscriptionStatus.Cancelled;
            subscription.CancelledAt ??= asOf;
            subscription.ScheduledBaseOfferingVersionId = null;
            tenant.BillingStatus = BillingStatus.Cancelled;
            EndItems(items, subscription.CurrentPeriodEnd, true);
        }
        else if (subscription.ScheduledBaseOfferingVersionId is { } scheduledVersionId)
        {
            var scheduledBase = items.SingleOrDefault(value =>
                value.ItemType == TenantSaasSubscriptionItemType.BasePlan &&
                value.Status == TenantSaasSubscriptionItemStatus.Scheduled &&
                value.OfferingVersionId == scheduledVersionId);
            if (scheduledBase is null)
            {
                var current = await dbContext.TenantSaasSubscriptions.AsNoTracking()
                    .SingleAsync(value => value.TenantId == tenantId && value.Id == subscription.Id, cancellationToken);
                if (current.ScheduledBaseOfferingVersionId is null &&
                    current.BaseOfferingVersionId == scheduledVersionId &&
                    current.LifecycleVersion != subscription.LifecycleVersion)
                {
                    dbContext.ChangeTracker.Clear();
                    return false;
                }

                throw new InvalidOperationException(
                    $"Scheduled base offering version '{scheduledVersionId}' has no matching subscription item.");
            }

            transition = "scheduled_plan_activated";
            var nextPeriodStart = subscription.CurrentPeriodEnd;
            EndItems(items.Where(value => value.Status == TenantSaasSubscriptionItemStatus.Active), nextPeriodStart,
                false);
            var scheduledItems = items.Where(value => value.Status == TenantSaasSubscriptionItemStatus.Scheduled)
                .ToArray();
            foreach (var item in scheduledItems)
            {
                item.Status = TenantSaasSubscriptionItemStatus.Active;
                item.StartsAt = nextPeriodStart;
            }

            subscription.BaseOfferingVersionId = scheduledVersionId;
            subscription.ScheduledBaseOfferingVersionId = null;
            subscription.Status = TenantSaasSubscriptionStatus.Active;
            subscription.CurrentPeriodStart = nextPeriodStart;
            subscription.CurrentPeriodEnd = scheduledBase.EndsAt;
            subscription.CancelAtPeriodEnd = false;
            subscription.CancelledAt = null;
            tenant.BillingStatus = BillingStatus.Active;
        }
        else if (subscription.Status == TenantSaasSubscriptionStatus.Trial)
        {
            transition = "trial_expired";
            subscription.Status = TenantSaasSubscriptionStatus.Expired;
            subscription.CancelledAt ??= asOf;
            tenant.BillingStatus = BillingStatus.Cancelled;
            EndItems(items, subscription.CurrentPeriodEnd, true);
        }
        else if (subscription.Status == TenantSaasSubscriptionStatus.Active)
        {
            transition = "payment_past_due";
            subscription.Status = TenantSaasSubscriptionStatus.PastDue;
            tenant.BillingStatus = BillingStatus.PastDue;
        }
        else if (subscription.Status == TenantSaasSubscriptionStatus.PastDue &&
                 asOf >= subscription.CurrentPeriodEnd.AddDays(Math.Clamp(options.PastDueGraceDays, 0, 365)))
        {
            transition = "past_due_expired";
            subscription.Status = TenantSaasSubscriptionStatus.Expired;
            subscription.CancelledAt ??= asOf;
            tenant.BillingStatus = BillingStatus.Cancelled;
            EndItems(items, subscription.CurrentPeriodEnd, true);
        }
        else
        {
            return false;
        }

        subscription.LifecycleVersion++;
        dbContext.AuditLogs.Add(new AuditLog
        {
            TenantId = tenantId,
            Action = $"platform_billing.subscription.{transition}",
            TargetType = "tenant_saas_subscriptions",
            TargetId = subscription.Id.ToString(),
            Details = JsonSerializer.SerializeToElement(new
            {
                previousStatus,
                subscription.Status,
                previousBaseVersionId,
                subscription.BaseOfferingVersionId,
                subscription.CurrentPeriodStart,
                subscription.CurrentPeriodEnd,
                subscription.LifecycleVersion
            })
        });

        try
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            dbContext.ChangeTracker.Clear();
            return false;
        }

        await services.GetRequiredService<ITenantFeatureCacheInvalidator>()
            .InvalidateAsync(tenantId, cancellationToken);

        return true;
    }

    private static void EndItems(
        IEnumerable<TenantSaasSubscriptionItem> items,
        DateTimeOffset periodEnd,
        bool cancelScheduled)
    {
        foreach (var item in items)
            if (item.Status == TenantSaasSubscriptionItemStatus.Active)
            {
                item.Status = TenantSaasSubscriptionItemStatus.Expired;
                if (periodEnd > item.StartsAt && item.EndsAt > periodEnd) item.EndsAt = periodEnd;
            }
            else if (cancelScheduled && item.Status == TenantSaasSubscriptionItemStatus.Scheduled)
            {
                item.Status = TenantSaasSubscriptionItemStatus.Cancelled;
            }
    }
}