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;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Polly;

namespace CMSMicroservice.Infrastructure.BackgroundJobs;

/// <summary>
/// Hangfire Job for weekly commission calculation
/// Executes every Sunday at 00:05 (Cron: "5 0 * * 0")
/// </summary>
public class WeeklyCommissionJob
{
    private readonly IMediator _mediator;
    private readonly ILogger<WeeklyCommissionJob> _logger;
    private readonly IApplicationDbContext _context;
    private readonly ResiliencePipeline _retryPipeline;

    public WeeklyCommissionJob(
        IMediator mediator,
        ILogger<WeeklyCommissionJob> logger,
        IApplicationDbContext context)
    {
        _mediator = mediator;
        _logger = logger;
        _context = context;

        // Polly Retry: 3 attempts, exponential backoff (5min → 10min → 20min)
        _retryPipeline = new ResiliencePipelineBuilder()
            .AddRetry(new Polly.Retry.RetryStrategyOptions
            {
                MaxRetryAttempts = 3,
                Delay = TimeSpan.FromMinutes(5),
                BackoffType = Polly.DelayBackoffType.Exponential,
                UseJitter = true,
                OnRetry = args =>
                {
                    _logger.LogWarning(
                        "⚠️ Retry attempt {AttemptNumber} after {Delay}ms delay. Exception: {ExceptionType}",
                        args.AttemptNumber,
                        args.RetryDelay.TotalMilliseconds,
                        args.Outcome.Exception?.GetType().Name ?? "None");
                    return ValueTask.CompletedTask;
                }
            })
            .Build();
    }

    /// <summary>
    /// Execute weekly commission calculation with retry logic
    /// Called by Hangfire scheduler
    /// </summary>
    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        var executionId = Guid.NewGuid();
        var startTime = DateTime.UtcNow;

        // Calculate for PREVIOUS week (completed week)
        var previousWeek = DateTime.UtcNow.AddDays(-7);
        var previousWeekNumber = GetWeekNumber(previousWeek);

        _logger.LogInformation(
            "🚀 [{ExecutionId}] Starting weekly commission calculation for {WeekNumber}",
            executionId, previousWeekNumber);

        // Create execution log entry
        var log = new WorkerExecutionLog
        {
            ExecutionId = executionId,
            WeekNumber = previousWeekNumber,
            StartedAt = startTime,
            Status = WorkerExecutionStatus.Running
        };
        _context.WorkerExecutionLogs.Add(log);
        await _context.SaveChangesAsync(cancellationToken);

        try
        {
            // Execute with retry pipeline
            await _retryPipeline.ExecuteAsync(async ct =>
            {
                await ExecuteWeeklyCalculationAsync(executionId, previousWeekNumber, ct);
            }, cancellationToken);

            // Update log on success
            var completedAt = DateTime.UtcNow;
            var duration = completedAt - startTime;

            log.Status = WorkerExecutionStatus.Success;
            log.CompletedAt = completedAt;
            log.DurationMs = (long)duration.TotalMilliseconds;

            // Get counts from database
            var balancesCount = await _context.NetworkWeeklyBalances
                .CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken);
            var payoutsCount = await _context.UserCommissionPayouts
                .CountAsync(x => x.WeekNumber == previousWeekNumber, cancellationToken);

            log.ProcessedCount = balancesCount + payoutsCount;

            await _context.SaveChangesAsync(cancellationToken);

            _logger.LogInformation(
                "✅ [{ExecutionId}] Completed successfully in {Duration}s | Balances: {BalancesCount}, Payouts: {PayoutsCount}",
                executionId, duration.TotalSeconds, balancesCount, payoutsCount);
        }
        catch (Exception ex)
        {
            // Update log on failure
            var completedAt = DateTime.UtcNow;
            var duration = completedAt - startTime;

            log.Status = WorkerExecutionStatus.Failed;
            log.CompletedAt = completedAt;
            log.DurationMs = (long)duration.TotalMilliseconds;
            log.ErrorMessage = ex.Message;
            log.ErrorStackTrace = ex.StackTrace;

            await _context.SaveChangesAsync(cancellationToken);

            _logger.LogError(ex,
                "❌ [{ExecutionId}] Failed after {Duration}s: {ErrorMessage}",
                executionId, duration.TotalSeconds, ex.Message);

            throw; // Re-throw for Hangfire to mark job as failed
        }
    }

    private async Task ExecuteWeeklyCalculationAsync(
        Guid executionId,
        string weekNumber,
        CancellationToken cancellationToken)
    {
        // Check idempotency: Skip if already calculated
        var existingPool = await _context.WeeklyCommissionPools
            .FirstOrDefaultAsync(x => x.WeekNumber == weekNumber, cancellationToken);

        if (existingPool != null && existingPool.IsCalculated)
        {
            _logger.LogWarning(
                "⚠️ [{ExecutionId}] Week {WeekNumber} already calculated. Skipping.",
                executionId, weekNumber);
            return;
        }

        using var transaction = new System.Transactions.TransactionScope(
            System.Transactions.TransactionScopeOption.Required,
            new System.Transactions.TransactionOptions
            {
                IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
                Timeout = TimeSpan.FromMinutes(30)
            },
            System.Transactions.TransactionScopeAsyncFlowOption.Enabled);

        try
        {
            // Step 1: Calculate user balances (Left/Right leg volumes)
            _logger.LogInformation(
                "📊 [{ExecutionId}] Step 1/3: Calculating weekly balances...",
                executionId);

            await _mediator.Send(new CalculateWeeklyBalancesCommand
            {
                WeekNumber = weekNumber,
                ForceRecalculate = false
            }, cancellationToken);

            // Step 2: Calculate global commission pool
            _logger.LogInformation(
                "💰 [{ExecutionId}] Step 2/3: Calculating commission pool...",
                executionId);

            await _mediator.Send(new CalculateWeeklyCommissionPoolCommand
            {
                WeekNumber = weekNumber,
                ForceRecalculate = false
            }, cancellationToken);

            // Step 3: Distribute commissions to users
            _logger.LogInformation(
                "💸 [{ExecutionId}] Step 3/3: Processing user payouts...",
                executionId);

            await _mediator.Send(new ProcessUserPayoutsCommand
            {
                WeekNumber = weekNumber,
                ForceReprocess = false
            }, cancellationToken);

            transaction.Complete();

            _logger.LogInformation(
                "✅ [{ExecutionId}] All 3 steps completed successfully",
                executionId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "❌ [{ExecutionId}] Transaction rolled back: {ErrorMessage}",
                executionId, ex.Message);
            throw;
        }
    }

    /// <summary>
    /// Get ISO 8601 week number (YYYY-Www format)
    /// </summary>
    private static string GetWeekNumber(DateTime date)
    {
        var calendar = System.Globalization.CultureInfo.InvariantCulture.Calendar;
        var weekNumber = calendar.GetWeekOfYear(
            date,
            System.Globalization.CalendarWeekRule.FirstFourDayWeek,
            DayOfWeek.Monday);

        var year = date.Year;
        if (weekNumber >= 52 && date.Month == 1)
            year--;
        else if (weekNumber == 1 && date.Month == 12)
            year++;

        return $"{year}-W{weekNumber:D2}";
    }
}
