using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;
using static Managing.Common.Enums;

namespace Managing.Application.Bots.Grains;

/// <summary>
/// Orleans grain for backtest trading bot operations.
/// Uses composition with TradingBotBase to maintain separation of concerns.
/// This grain is stateless and follows the exact pattern of GetBacktestingResult from Backtester.cs.
/// </summary>
[StatelessWorker]
public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
{
    private readonly ILogger<BacktestTradingBotGrain> _logger;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly IBacktestRepository _backtestRepository;
    private bool _isDisposed = false;

    public BacktestTradingBotGrain(
        ILogger<BacktestTradingBotGrain> logger,
        IServiceScopeFactory scopeFactory,
        IBacktestRepository backtestRepository)
    {
        _logger = logger;
        _scopeFactory = scopeFactory;
        _backtestRepository = backtestRepository;
    }

    /// <summary>
    /// Runs a complete backtest following the exact pattern of GetBacktestingResult from Backtester.cs
    /// </summary>
    /// <param name="config">The trading bot configuration for this backtest</param>
    /// <param name="candles">The candles to use for backtesting</param>
    /// <param name="withCandles">Whether to include candles and indicators values in the response</param>
    /// <param name="requestId">The request ID to associate with this backtest</param>
    /// <param name="metadata">Additional metadata to associate with this backtest</param>
    /// <returns>The complete backtest result</returns>
    public async Task<LightBacktest> RunBacktestAsync(
        TradingBotConfig config,
        HashSet<Candle> candles,
        User user = null,
        bool save = false,
        bool withCandles = false,
        string requestId = null,
        object metadata = null)
    {
        if (candles == null || candles.Count == 0)
        {
            throw new Exception("No candle to backtest");
        }

        // Create a fresh TradingBotBase instance for this backtest
        var tradingBot = await CreateTradingBotInstance(config);
        tradingBot.Account = user.Accounts.First(a => a.Name == config.AccountName);

        var totalCandles = candles.Count;
        var currentCandle = 0;
        var lastLoggedPercentage = 0;

        _logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
            totalCandles, config.Ticker, config.Timeframe);

        // Initialize wallet balance with first candle
        tradingBot.WalletBalances.Clear();
        tradingBot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);

        var fixedCandles = new HashSet<Candle>();
        // Process all candles following the exact pattern from GetBacktestingResult
        foreach (var candle in candles)
        {
            fixedCandles.Add(candle);
            tradingBot.LastCandle = candle;

            // Update signals manually only for backtesting
            await tradingBot.UpdateSignals(fixedCandles);
            await tradingBot.Run();

            currentCandle++;

            // Check if wallet balance fell below 10 USDC and break if so
            var currentWalletBalance = tradingBot.WalletBalances.Values.LastOrDefault();
            if (currentWalletBalance < 10m)
            {
                _logger.LogWarning(
                    "Backtest stopped early: Wallet balance fell below 10 USDC (Current: {CurrentBalance:F2} USDC) at candle {CurrentCandle}/{TotalCandles} from {CandleDate}",
                    currentWalletBalance, currentCandle, totalCandles, candle.Date.ToString("yyyy-MM-dd HH:mm"));
                break;
            }
        }

        _logger.LogInformation("Backtest processing completed. Calculating final results...");

        var finalPnl = tradingBot.GetProfitAndLoss();
        var winRate = tradingBot.GetWinRate();
        var stats = TradingHelpers.GetStatistics(tradingBot.WalletBalances);
        var growthPercentage =
            TradingHelpers.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, finalPnl);
        var hodlPercentage = TradingHelpers.GetHodlPercentage(candles.First(), candles.Last());

        var fees = tradingBot.GetTotalFees();
        var scoringParams = new BacktestScoringParams(
            sharpeRatio: (double)stats.SharpeRatio,
            growthPercentage: (double)growthPercentage,
            hodlPercentage: (double)hodlPercentage,
            winRate: winRate,
            totalPnL: (double)finalPnl,
            fees: (double)fees,
            tradeCount: tradingBot.Positions.Count,
            maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime,
            maxDrawdown: stats.MaxDrawdown,
            initialBalance: tradingBot.WalletBalances.FirstOrDefault().Value,
            tradingBalance: config.BotTradingBalance,
            startDate: candles.First().Date,
            endDate: candles.Last().Date,
            timeframe: config.Timeframe,
            moneyManagement: config.MoneyManagement
        );

        var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);

        // Generate requestId if not provided
        var finalRequestId = requestId ?? Guid.NewGuid().ToString();

        // Create backtest result with conditional candles and indicators values
        var result = new Backtest(config, tradingBot.Positions, tradingBot.Signals,
            withCandles ? candles : new HashSet<Candle>())
        {
            FinalPnl = finalPnl,
            WinRate = winRate,
            GrowthPercentage = growthPercentage,
            HodlPercentage = hodlPercentage,
            Fees = fees,
            WalletBalances = tradingBot.WalletBalances.ToList(),
            Statistics = stats,
            Score = scoringResult.Score,
            ScoreMessage = scoringResult.GenerateSummaryMessage(),
            Id = Guid.NewGuid().ToString(),
            RequestId = finalRequestId,
            Metadata = metadata,
            StartDate = candles.FirstOrDefault()!.OpenTime,
            EndDate = candles.LastOrDefault()!.OpenTime,
        };

        if (save && user != null)
        {
            _backtestRepository.InsertBacktestForUser(user, result);
        }

        // Send notification if backtest meets criteria
        await SendBacktestNotificationIfCriteriaMet(result);

        // Convert Backtest to LightBacktest for safe Orleans serialization
        return ConvertToLightBacktest(result);
    }

    /// <summary>
    /// Converts a Backtest to LightBacktest for safe Orleans serialization
    /// </summary>
    /// <param name="backtest">The full backtest to convert</param>
    /// <returns>A lightweight backtest suitable for Orleans serialization</returns>
    private LightBacktest ConvertToLightBacktest(Backtest backtest)
    {
        return new LightBacktest
        {
            Id = backtest.Id,
            Config = backtest.Config,
            FinalPnl = backtest.FinalPnl,
            WinRate = backtest.WinRate,
            GrowthPercentage = backtest.GrowthPercentage,
            HodlPercentage = backtest.HodlPercentage,
            StartDate = backtest.StartDate,
            EndDate = backtest.EndDate,
            MaxDrawdown = backtest.Statistics?.MaxDrawdown,
            Fees = backtest.Fees,
            SharpeRatio = (double?)backtest.Statistics?.SharpeRatio,
            Score = backtest.Score,
            ScoreMessage = backtest.ScoreMessage
        };
    }

    /// <summary>
    /// Creates a TradingBotBase instance using composition for backtesting
    /// </summary>
    private async Task<TradingBotBase> CreateTradingBotInstance(TradingBotConfig config, User user = null)
    {
        // Validate configuration for backtesting
        if (config == null)
        {
            throw new InvalidOperationException("Bot configuration is not initialized");
        }

        if (!config.IsForBacktest)
        {
            throw new InvalidOperationException("BacktestTradingBotGrain can only be used for backtesting");
        }

        // Create the trading bot instance
        var logger = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<TradingBotBase>>();
        var tradingBot = new TradingBotBase(logger, _scopeFactory, config);
        return tradingBot;
    }

    /// <summary>
    /// Sends notification if backtest meets criteria (following Backtester.cs pattern)
    /// </summary>
    private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
    {
        try
        {
            if (backtest.Score > 60)
            {
                // Note: In a real implementation, you would inject IMessengerService
                // For now, we'll just log the notification
                _logger.LogInformation("Backtest {BacktestId} scored {Score} - notification criteria met",
                    backtest.Id, backtest.Score);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send backtest notification for backtest {Id}", backtest.Id);
        }
    }

    /// <summary>
    /// Aggregates indicator values (following Backtester.cs pattern)
    /// </summary>
    private Dictionary<IndicatorType, IndicatorsResultBase> AggregateValues(
        Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues)
    {
        var result = new Dictionary<IndicatorType, IndicatorsResultBase>();
        foreach (var indicator in indicatorsValues)
        {
            result[indicator.Key] = indicator.Value;
        }

        return result;
    }

    /// <summary>
    /// Gets indicators values (following Backtester.cs pattern)
    /// </summary>
    private Dictionary<IndicatorType, IndicatorsResultBase> GetIndicatorsValues(List<LightIndicator> indicators,
        HashSet<Candle> candles)
    {
        var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();

        foreach (var indicator in indicators)
        {
            try
            {
                var builtIndicator = ScenarioHelpers.BuildIndicator(indicator);
                indicatorsValues[indicator.Type] = builtIndicator.GetIndicatorValues(candles);
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error building indicator {IndicatorType}", indicator.Type);
            }
        }

        return indicatorsValues;
    }

    public void Dispose()
    {
        if (!_isDisposed)
        {
            _isDisposed = true;
        }
    }
}