using System.Text.Json;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Orleans;
using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;

namespace Managing.Application.Grains;

/// <summary>
/// Stateless worker grain for processing bundle backtest requests
/// Uses the bundle request ID as the primary key (Guid)
/// Implements IRemindable for automatic retry of failed bundles
/// Uses custom compute placement with random fallback.
/// </summary>
[StatelessWorker]
[TradingPlacement] // Use custom compute placement with random fallback
public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
{
    private readonly ILogger<BundleBacktestGrain> _logger;
    private readonly IServiceScopeFactory _scopeFactory;

    // Reminder configuration
    private const string RETRY_REMINDER_NAME = "BundleBacktestRetry";
    private static readonly TimeSpan RETRY_INTERVAL = TimeSpan.FromMinutes(30);

    public BundleBacktestGrain(
        ILogger<BundleBacktestGrain> logger,
        IServiceScopeFactory scopeFactory)
    {
        _logger = logger;
        _scopeFactory = scopeFactory;
    }

    public async Task ProcessBundleRequestAsync()
    {
        // Get the RequestId from the grain's primary key
        var bundleRequestId = this.GetPrimaryKey();

        try
        {
            // Create a new service scope to get fresh instances of services with scoped DbContext
            using var scope = _scopeFactory.CreateScope();
            var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
            var messengerService = scope.ServiceProvider.GetRequiredService<IMessengerService>();

            // Get the specific bundle request by ID
            var bundleRequest = await GetBundleRequestById(backtester, bundleRequestId);
            if (bundleRequest == null)
            {
                _logger.LogError("Bundle request {RequestId} not found", bundleRequestId);
                return;
            }

            // Process only this specific bundle request
            await ProcessBundleRequest(bundleRequest, backtester, messengerService);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in BundleBacktestGrain for request {RequestId}", bundleRequestId);
            throw;
        }
    }

    private async Task<BundleBacktestRequest> GetBundleRequestById(IBacktester backtester, Guid bundleRequestId)
    {
        try
        {
            // Get pending and failed bundle backtest requests for retry capability
            var pendingRequests =
                await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending);
            var failedRequests =
                await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Failed);

            var allRequests = pendingRequests.Concat(failedRequests);
            return allRequests.FirstOrDefault(r => r.RequestId == bundleRequestId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to get bundle request {RequestId}", bundleRequestId);
            return null;
        }
    }

    private async Task ProcessBundleRequest(
        BundleBacktestRequest bundleRequest,
        IBacktester backtester,
        IMessengerService messengerService)
    {
        try
        {
            _logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);

            // Update status to running
            bundleRequest.Status = BundleBacktestRequestStatus.Running;
            await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

            // Deserialize the backtest requests as strongly-typed objects
            var backtestRequests =
                JsonSerializer.Deserialize<List<RunBacktestRequest>>(bundleRequest.BacktestRequestsJson);
            if (backtestRequests == null)
            {
                throw new InvalidOperationException("Failed to deserialize backtest requests");
            }

            // Process each backtest request sequentially
            for (int i = 0; i < backtestRequests.Count; i++)
            {
                await ProcessSingleBacktest(backtester, backtestRequests[i], bundleRequest, i);
            }

            // Update final status and send notifications
            await UpdateFinalStatus(bundleRequest, backtester, messengerService);

            _logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
                bundleRequest.RequestId, bundleRequest.Status);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing bundle backtest request {RequestId}", bundleRequest.RequestId);
            await HandleBundleRequestError(bundleRequest, backtester, ex);
        }
    }

    private async Task ProcessSingleBacktest(
        IBacktester backtester,
        RunBacktestRequest runBacktestRequest,
        BundleBacktestRequest bundleRequest,
        int index)
    {
        try
        {
            // Get total count from deserialized requests instead of string splitting
            var backtestRequests =
                JsonSerializer.Deserialize<List<RunBacktestRequest>>(bundleRequest.BacktestRequestsJson);
            var totalCount = backtestRequests?.Count ?? 0;

            // Update current backtest being processed
            bundleRequest.CurrentBacktest = $"Backtest {index + 1} of {totalCount}";
            await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

            bundleRequest.User.Accounts = await ServiceScopeHelpers.WithScopedService<IAccountService, List<Account>>(
                _scopeFactory,
                async service => { return (await service.GetAccountsByUserAsync(bundleRequest.User, true)).ToList(); });
            // Run the backtest directly with the strongly-typed request
            var backtestId = await RunSingleBacktest(backtester, runBacktestRequest, bundleRequest, index);
            if (!string.IsNullOrEmpty(backtestId))
            {
                bundleRequest.Results.Add(backtestId);
            }

            // Update progress
            bundleRequest.CompletedBacktests++;
            await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

            _logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
                index + 1, bundleRequest.RequestId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
                index + 1, bundleRequest.RequestId);
            bundleRequest.FailedBacktests++;
            await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
        }
    }

    private async Task<string> RunSingleBacktest(
        IBacktester backtester,
        RunBacktestRequest runBacktestRequest,
        BundleBacktestRequest bundleRequest,
        int index)
    {
        if (runBacktestRequest?.Config == null)
        {
            _logger.LogError("Invalid RunBacktestRequest in bundle (null config)");
            return string.Empty;
        }

        // Map MoneyManagement
        MoneyManagement moneyManagement = null;
        if (!string.IsNullOrEmpty(runBacktestRequest.Config.MoneyManagementName))
        {
            _logger.LogWarning("MoneyManagementName provided but cannot resolve in grain context: {Name}",
                runBacktestRequest.Config.MoneyManagementName);
        }
        else if (runBacktestRequest.Config.MoneyManagement != null)
        {
            var mmReq = runBacktestRequest.Config.MoneyManagement;
            moneyManagement = new MoneyManagement
            {
                Name = mmReq.Name,
                Timeframe = mmReq.Timeframe,
                StopLoss = mmReq.StopLoss,
                TakeProfit = mmReq.TakeProfit,
                Leverage = mmReq.Leverage
            };
            moneyManagement.FormatPercentage();
        }

        // Map Scenario
        LightScenario scenario = null;
        if (runBacktestRequest.Config.Scenario != null)
        {
            var sReq = runBacktestRequest.Config.Scenario;
            scenario = new LightScenario(sReq.Name, sReq.LoopbackPeriod)
            {
                Indicators = sReq.Indicators?.Select(i => new LightIndicator(i.Name, i.Type)
                {
                    SignalType = i.SignalType,
                    MinimumHistory = i.MinimumHistory,
                    Period = i.Period,
                    FastPeriods = i.FastPeriods,
                    SlowPeriods = i.SlowPeriods,
                    SignalPeriods = i.SignalPeriods,
                    Multiplier = i.Multiplier,
                    SmoothPeriods = i.SmoothPeriods,
                    StochPeriods = i.StochPeriods,
                    CyclePeriods = i.CyclePeriods
                }).ToList() ?? new List<LightIndicator>()
            };
        }

        // Map TradingBotConfig
        var backtestConfig = new TradingBotConfig
        {
            AccountName = runBacktestRequest.Config.AccountName,
            MoneyManagement = moneyManagement,
            Ticker = runBacktestRequest.Config.Ticker,
            ScenarioName = runBacktestRequest.Config.ScenarioName,
            Scenario = scenario,
            Timeframe = runBacktestRequest.Config.Timeframe,
            IsForWatchingOnly = runBacktestRequest.Config.IsForWatchingOnly,
            BotTradingBalance = runBacktestRequest.Config.BotTradingBalance,
            IsForBacktest = true,
            CooldownPeriod = runBacktestRequest.Config.CooldownPeriod ?? 1,
            MaxLossStreak = runBacktestRequest.Config.MaxLossStreak,
            MaxPositionTimeHours = runBacktestRequest.Config.MaxPositionTimeHours,
            FlipOnlyWhenInProfit = runBacktestRequest.Config.FlipOnlyWhenInProfit,
            FlipPosition = runBacktestRequest.Config.FlipPosition,
            Name = $"{bundleRequest.Name} #{index + 1}",
            CloseEarlyWhenProfitable = runBacktestRequest.Config.CloseEarlyWhenProfitable,
            UseSynthApi = runBacktestRequest.Config.UseSynthApi,
            UseForPositionSizing = runBacktestRequest.Config.UseForPositionSizing,
            UseForSignalFiltering = runBacktestRequest.Config.UseForSignalFiltering,
            UseForDynamicStopLoss = runBacktestRequest.Config.UseForDynamicStopLoss
        };

        // Run the backtest
        var result = await backtester.RunTradingBotBacktest(
            backtestConfig,
            runBacktestRequest.StartDate,
            runBacktestRequest.EndDate,
            bundleRequest.User,
            true,
            runBacktestRequest.WithCandles,
            bundleRequest.RequestId.ToString()
        );

        _logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequest.RequestId);
        return result.Id;
    }

    private async Task UpdateFinalStatus(
        BundleBacktestRequest bundleRequest,
        IBacktester backtester,
        IMessengerService messengerService)
    {
        if (bundleRequest.FailedBacktests == 0)
        {
            bundleRequest.Status = BundleBacktestRequestStatus.Completed;
            await NotifyUser(bundleRequest, messengerService);
        }
        else if (bundleRequest.CompletedBacktests == 0)
        {
            bundleRequest.Status = BundleBacktestRequestStatus.Failed;
            bundleRequest.ErrorMessage = "All backtests failed";
        }
        else
        {
            bundleRequest.Status = BundleBacktestRequestStatus.Completed;
            bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed";
            await NotifyUser(bundleRequest, messengerService);
        }

        bundleRequest.CompletedAt = DateTime.UtcNow;
        bundleRequest.CurrentBacktest = null;
        await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

        // Unregister retry reminder since bundle completed
        await UnregisterRetryReminder();
    }

    private async Task HandleBundleRequestError(
        BundleBacktestRequest bundleRequest,
        IBacktester backtester,
        Exception ex)
    {
        bundleRequest.Status = BundleBacktestRequestStatus.Failed;
        bundleRequest.ErrorMessage = ex.Message;
        bundleRequest.CompletedAt = DateTime.UtcNow;
        await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

        // Register retry reminder for failed bundle
        await RegisterRetryReminder();
    }

    private async Task NotifyUser(BundleBacktestRequest bundleRequest, IMessengerService messengerService)
    {
        if (bundleRequest.User?.TelegramChannel != null)
        {
            var message = bundleRequest.FailedBacktests == 0
                ? $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed successfully."
                : $"⚠️ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed with {bundleRequest.FailedBacktests} failed backtests.";

            await messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
        }
    }

    #region IRemindable Implementation

    /// <summary>
    /// Handles reminder callbacks for automatic retry of failed bundle backtests
    /// </summary>
    public async Task ReceiveReminder(string reminderName, TickStatus status)
    {
        if (reminderName != RETRY_REMINDER_NAME)
        {
            _logger.LogWarning("Unknown reminder {ReminderName} received", reminderName);
            return;
        }

        var bundleRequestId = this.GetPrimaryKey();
        _logger.LogInformation("Retry reminder triggered for bundle request {RequestId}", bundleRequestId);

        try
        {
            using var scope = _scopeFactory.CreateScope();
            var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();

            // Get the bundle request
            var bundleRequest = await GetBundleRequestById(backtester, bundleRequestId);
            if (bundleRequest == null)
            {
                _logger.LogWarning("Bundle request {RequestId} not found during retry", bundleRequestId);
                await UnregisterRetryReminder();
                return;
            }

            // Check if bundle is still failed
            if (bundleRequest.Status != BundleBacktestRequestStatus.Failed)
            {
                _logger.LogInformation(
                    "Bundle request {RequestId} is no longer failed (status: {Status}), unregistering reminder",
                    bundleRequestId, bundleRequest.Status);
                await UnregisterRetryReminder();
                return;
            }

            // Retry the bundle processing
            _logger.LogInformation("Retrying failed bundle request {RequestId}", bundleRequestId);

            // Reset status to pending for retry
            bundleRequest.Status = BundleBacktestRequestStatus.Pending;
            bundleRequest.ErrorMessage = null;
            bundleRequest.CurrentBacktest = null;
            await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);

            // Process the bundle again
            await ProcessBundleRequestAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during bundle backtest retry for request {RequestId}", bundleRequestId);
        }
    }

    /// <summary>
    /// Registers a retry reminder for this bundle request
    /// </summary>
    private async Task RegisterRetryReminder()
    {
        try
        {
            await this.RegisterOrUpdateReminder(RETRY_REMINDER_NAME, RETRY_INTERVAL, RETRY_INTERVAL);
            _logger.LogInformation("Registered retry reminder for bundle request {RequestId}", this.GetPrimaryKey());
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to register retry reminder for bundle request {RequestId}",
                this.GetPrimaryKey());
        }
    }

    /// <summary>
    /// Unregisters the retry reminder for this bundle request
    /// </summary>
    private async Task UnregisterRetryReminder()
    {
        try
        {
            var reminder = await this.GetReminder(RETRY_REMINDER_NAME);
            if (reminder != null)
            {
                await this.UnregisterReminder(reminder);
                _logger.LogInformation("Unregistered retry reminder for bundle request {RequestId}",
                    this.GetPrimaryKey());
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to unregister retry reminder for bundle request {RequestId}",
                this.GetPrimaryKey());
        }
    }

    #endregion
}