using System.Globalization;
using System.Transactions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;
using CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool;
using CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.Commission;
using Polly;
using Polly.Retry;

namespace CMSMicroservice.Infrastructure.BackgroundJobs;

/// <summary>
/// Background Worker برای محاسبه و توزیع کمیسیون‌های هفتگی شبکه
/// زمان اجرا: هر یکشنبه ساعت 23:59
/// </summary>
public class WeeklyNetworkCommissionWorker : BackgroundService
{
    private readonly ILogger<WeeklyNetworkCommissionWorker> _logger;
    private readonly IServiceProvider _serviceProvider;
    private Timer? _timer;
    private readonly ResiliencePipeline _retryPipeline;

    public WeeklyNetworkCommissionWorker(
        ILogger<WeeklyNetworkCommissionWorker> logger,
        IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
        
        // ایجاد Retry Policy با Exponential Backoff
        _retryPipeline = new ResiliencePipelineBuilder()
            .AddRetry(new RetryStrategyOptions
            {
                MaxRetryAttempts = 3,
                Delay = TimeSpan.FromMinutes(5),
                BackoffType = DelayBackoffType.Exponential,
                UseJitter = true,
                OnRetry = args =>
                {
                    _logger.LogWarning(
                        "Retry attempt {AttemptNumber} after {Delay}ms due to: {Exception}",
                        args.AttemptNumber,
                        args.RetryDelay.TotalMilliseconds,
                        args.Outcome.Exception?.Message);
                    return ValueTask.CompletedTask;
                }
            })
            .Build();
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Weekly Network Commission Worker started at: {Time} (Local Time)", DateTime.Now);

        // محاسبه زمان تا یکشنبه بعدی ساعت 23:59
        var now = DateTime.Now;
        var nextSunday = GetNextSunday(now);
        var nextRunTime = new DateTime(nextSunday.Year, nextSunday.Month, nextSunday.Day, 23, 59, 0);
        
        var delay = nextRunTime - now;
        if (delay.TotalMilliseconds < 0)
        {
            // اگر زمان گذشته باشد، یکشنبه بعدی
            nextRunTime = nextRunTime.AddDays(7);
            delay = nextRunTime - now;
        }

        _logger.LogInformation("Next execution scheduled for: {NextRun}", nextRunTime);

        // تنظیم timer برای اجرا در زمان مشخص و تکرار هفتگی با Retry
        _timer = new Timer(
            callback: async _ => await _retryPipeline.ExecuteAsync(
                async ct => await ExecuteWeeklyCalculationAsync(ct),
                stoppingToken),
            state: null,
            dueTime: delay,
            period: TimeSpan.FromDays(7) // هر 7 روز یکبار
        );

        return Task.CompletedTask;
    }

    /// <summary>
    /// محاسبه تاریخ یکشنبه بعدی
    /// </summary>
    private static DateTime GetNextSunday(DateTime from)
    {
        var daysUntilSunday = ((int)DayOfWeek.Sunday - (int)from.DayOfWeek + 7) % 7;
        if (daysUntilSunday == 0)
        {
            // اگر امروز یکشنبه است و ساعت گذشته، یکشنبه بعدی
            if (from.TimeOfDay > new TimeSpan(23, 59, 0))
            {
                daysUntilSunday = 7;
            }
        }
        return from.Date.AddDays(daysUntilSunday);
    }

    /// <summary>
    /// اجرای محاسبات هفتگی کمیسیون
    /// </summary>
    private async Task ExecuteWeeklyCalculationAsync(CancellationToken cancellationToken)
    {
        var executionId = Guid.NewGuid();
        var startTime = DateTime.UtcNow;
        _logger.LogInformation("=== Starting Weekly Commission Calculation [{ExecutionId}] at {Time} (UTC) ===", 
            executionId, startTime);

        WorkerExecutionLog? log = null;

        try
        {
            using var scope = _serviceProvider.CreateScope();
            var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
            var context = scope.ServiceProvider.GetRequiredService<IApplicationDbContext>();

            // دریافت شماره هفته قبل (هفته‌ای که باید محاسبه شود)
            var previousWeekNumber = GetPreviousWeekNumber();
            var currentWeekNumber = GetCurrentWeekNumber();

            _logger.LogInformation("Processing week: {WeekNumber}", previousWeekNumber);

            // ایجاد Log
            log = new WorkerExecutionLog
            {
                ExecutionId = executionId,
                WeekNumber = previousWeekNumber,
                StartedAt = startTime,
                Status = WorkerExecutionStatus.Running,
                ProcessedCount = 0,
                ErrorCount = 0
            };
            await context.WorkerExecutionLogs.AddAsync(log, cancellationToken);
            await context.SaveChangesAsync(cancellationToken);

            // ===== IDEMPOTENCY CHECK =====
            // بررسی اینکه آیا این هفته قبلاً محاسبه شده یا نه
            var existingPool = await context.WeeklyCommissionPools
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken);

            if (existingPool?.IsCalculated == true)
            {
                _logger.LogWarning(
                    "Week {WeekNumber} already calculated. Skipping execution [{ExecutionId}]",
                    previousWeekNumber, executionId);
                
                // Update log
                log.Status = WorkerExecutionStatus.SuccessWithWarnings;
                log.CompletedAt = DateTime.UtcNow;
                log.DurationMs = (long)(log.CompletedAt.Value - log.StartedAt).TotalMilliseconds;
                log.Details = "Week already calculated - skipped";
                await context.SaveChangesAsync(cancellationToken);
                return;
            }

            // ===== TRANSACTION SCOPE =====
            // تمام مراحل باید داخل یک تراکنش باشند برای Atomicity
            using var transaction = new TransactionScope(
                TransactionScopeOption.Required,
                new TransactionOptions
                {
                    IsolationLevel = IsolationLevel.ReadCommitted,
                    Timeout = TimeSpan.FromMinutes(30) // برای شبکه‌های بزرگ
                },
                TransactionScopeAsyncFlowOption.Enabled);

            int balancesCalculated = 0;
            long poolValue = 0;
            int payoutsProcessed = 0;

            try
            {
                // مرحله 1: محاسبه تعادل‌های شبکه
                _logger.LogInformation("Step 1/4: Calculating network balances for week {WeekNumber}", previousWeekNumber);
                balancesCalculated = await mediator.Send(new CalculateWeeklyBalancesCommand
                {
                    WeekNumber = previousWeekNumber,
                    ForceRecalculate = false
                }, cancellationToken);
                _logger.LogInformation("Network balances calculated: {Count} users processed", balancesCalculated);

                // مرحله 2: محاسبه استخر کمیسیون و ارزش هر امتیاز
                _logger.LogInformation("Step 2/4: Calculating commission pool for week {WeekNumber}", previousWeekNumber);
                poolValue = await mediator.Send(new CalculateWeeklyCommissionPoolCommand
                {
                    WeekNumber = previousWeekNumber,
                    ForceRecalculate = false
                }, cancellationToken);
                _logger.LogInformation("Commission pool calculated. Value per balance: {Value:N0} Rials", poolValue);

                // مرحله 3: توزیع کمیسیون‌ها به کاربران
                _logger.LogInformation("Step 3/4: Processing user payouts for week {WeekNumber}", previousWeekNumber);
                payoutsProcessed = await mediator.Send(new ProcessUserPayoutsCommand
                {
                    WeekNumber = previousWeekNumber,
                    ForceReprocess = false
                }, cancellationToken);
                _logger.LogInformation("User payouts processed: {Count} payouts created", payoutsProcessed);

                // ===== مرحله 4 (گام 5 در مستندات): ریست/Expire کردن تعادل‌های هفته قبل =====
                _logger.LogInformation("Step 4/4: Expiring weekly balances for week {WeekNumber}", previousWeekNumber);
                var balancesToExpire = await context.NetworkWeeklyBalances
                    .Where(x => x.WeekNumber == previousWeekNumber && !x.IsExpired)
                    .ToListAsync(cancellationToken);

                foreach (var balance in balancesToExpire)
                {
                    balance.IsExpired = true;
                }

                await context.SaveChangesAsync(cancellationToken);
                _logger.LogInformation("Expired {Count} balance records", balancesToExpire.Count);

                // Commit Transaction
                transaction.Complete();

                var completedAt = DateTime.UtcNow;
                var duration = completedAt - startTime;
                
                // Update log - Success
                if (log != null)
                {
                    log.Status = WorkerExecutionStatus.Success;
                    log.CompletedAt = completedAt;
                    log.DurationMs = (long)duration.TotalMilliseconds;
                    log.ProcessedCount = balancesCalculated + payoutsProcessed;
                    log.Details = $"Success: {balancesCalculated} balances, {payoutsProcessed} payouts, {balancesToExpire.Count} expired";
                    await context.SaveChangesAsync(cancellationToken);
                }

                _logger.LogInformation(
                    "=== Weekly Commission Calculation Completed Successfully [{ExecutionId}] ===" +
                    "\n  Week: {WeekNumber}" +
                    "\n  Users Processed: {UserCount}" +
                    "\n  Value Per Balance: {ValuePerBalance:N0} Rials" +
                    "\n  Payouts Created: {PayoutCount}" +
                    "\n  Balances Expired: {ExpiredCount}" +
                    "\n  Duration: {Duration:mm\\:ss}",
                    executionId,
                    previousWeekNumber,
                    balancesCalculated,
                    poolValue,
                    payoutsProcessed,
                    balancesToExpire.Count,
                    duration
                );

                // Send success notification to admin
                using var successScope = _serviceProvider.CreateScope();
                var alertService = successScope.ServiceProvider.GetRequiredService<IAlertService>();
                
                await alertService.SendSuccessNotificationAsync(
                    "Weekly Commission Completed",
                    $"Week {previousWeekNumber}: {payoutsProcessed} payouts, {balancesToExpire.Count} balances expired");

                // TODO: Send notifications to users who received commission
                // await NotifyUsersAboutPayouts(payoutsProcessed, previousWeekNumber);
            }
            catch (Exception innerEx)
            {
                _logger.LogError(innerEx,
                    "Transaction failed during step execution. Rolling back. [{ExecutionId}]",
                    executionId);
                // Transaction will auto-rollback when scope is disposed without Complete()
                throw;
            }
        }
        catch (Exception ex)
        {
            var previousWeekNumber = GetPreviousWeekNumber();
            
            _logger.LogCritical(ex, 
                "!!! CRITICAL ERROR in Weekly Commission Calculation [{ExecutionId}] !!!" +
                "\n  Week: {WeekNumber}" +
                "\n  Message: {Message}" +
                "\n  StackTrace: {StackTrace}" +
                "\n  Please investigate immediately!", 
                executionId,
                previousWeekNumber,
                ex.Message,
                ex.StackTrace);
            
            // Update log - Failed
            if (log != null)
            {
                try
                {
                    using var errorScope = _serviceProvider.CreateScope();
                    var context = errorScope.ServiceProvider.GetRequiredService<IApplicationDbContext>();
                    
                    log.Status = WorkerExecutionStatus.Failed;
                    log.CompletedAt = DateTime.UtcNow;
                    log.DurationMs = (long)(log.CompletedAt.Value - log.StartedAt).TotalMilliseconds;
                    log.ErrorCount = 1;
                    log.ErrorMessage = ex.Message;
                    log.ErrorStackTrace = ex.StackTrace;
                    
                    await context.SaveChangesAsync(cancellationToken);
                }
                catch (Exception logEx)
                {
                    _logger.LogError(logEx, "Failed to update error log");
                }
            }
            
            // ===== ERROR HANDLING & ALERTING =====
            // در محیط production باید Alert/Notification ارسال شود
            
            using var alertScope = _serviceProvider.CreateScope();
            var alertService = alertScope.ServiceProvider.GetRequiredService<IAlertService>();
            
            await alertService.SendCriticalAlertAsync(
                "Weekly Commission Worker Failed",
                $"Worker execution {executionId} failed for week {previousWeekNumber}. Will retry with exponential backoff.",
                ex,
                cancellationToken);
            
            // Retry با Polly - اگر همچنان fail کند exception throw می‌شود
            throw;
        }
    }

    /// <summary>
    /// دریافت شماره هفته جاری (فرمت ISO 8601: YYYY-Www)
    /// </summary>
    private static string GetCurrentWeekNumber()
    {
        var today = DateTime.Today;
        var calendar = CultureInfo.CurrentCulture.Calendar;
        var weekNumber = calendar.GetWeekOfYear(
            today,
            CalendarWeekRule.FirstFourDayWeek,
            DayOfWeek.Monday
        );
        return $"{today.Year}-W{weekNumber:D2}";
    }

    /// <summary>
    /// دریافت شماره هفته قبل
    /// </summary>
    private static string GetPreviousWeekNumber()
    {
        var lastWeek = DateTime.Today.AddDays(-7);
        var calendar = CultureInfo.CurrentCulture.Calendar;
        var weekNumber = calendar.GetWeekOfYear(
            lastWeek,
            CalendarWeekRule.FirstFourDayWeek,
            DayOfWeek.Monday
        );
        return $"{lastWeek.Year}-W{weekNumber:D2}";
    }

    public override void Dispose()
    {
        _timer?.Dispose();
        base.Dispose();
    }
}
