using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using LightBacktestResponse = Managing.Domain.Backtests.LightBacktest; // Use the domain model for notification

namespace Managing.Application.Backtests
{
    public class Backtester : IBacktester
    {
        private readonly IBacktestRepository _backtestRepository;
        private readonly ILogger<Backtester> _logger;
        private readonly IExchangeService _exchangeService;
        private readonly IScenarioService _scenarioService;
        private readonly IAccountService _accountService;
        private readonly IMessengerService _messengerService;
        private readonly IKaigenService _kaigenService;
        private readonly IHubContext<BacktestHub> _hubContext;
        private readonly IGrainFactory _grainFactory;

        public Backtester(
            IExchangeService exchangeService,
            IBacktestRepository backtestRepository,
            ILogger<Backtester> logger,
            IScenarioService scenarioService,
            IAccountService accountService,
            IMessengerService messengerService,
            IKaigenService kaigenService,
            IHubContext<BacktestHub> hubContext,
            IGrainFactory grainFactory)
        {
            _exchangeService = exchangeService;
            _backtestRepository = backtestRepository;
            _logger = logger;
            _scenarioService = scenarioService;
            _accountService = accountService;
            _messengerService = messengerService;
            _kaigenService = kaigenService;
            _hubContext = hubContext;
            _grainFactory = grainFactory;
        }

        /// <summary>
        /// Runs a trading bot backtest with the specified configuration and date range.
        /// Automatically handles different bot types based on config.BotType.
        /// </summary>
        /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
        /// <param name="startDate">The start date for the backtest</param>
        /// <param name="endDate">The end date for the backtest</param>
        /// <param name="user">The user running the backtest (optional)</param>
        /// <param name="save">Whether to save the backtest results</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 (optional)</param>
        /// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
        /// <returns>The lightweight backtest results</returns>
        public async Task<LightBacktestResponse> RunTradingBotBacktest(
            TradingBotConfig config,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool save = false,
            bool withCandles = false,
            string requestId = null,
            object metadata = null)
        {
            string creditRequestId = null;

            // Debit user credits before starting the backtest
            if (user != null)
            {
                try
                {
                    creditRequestId = await _kaigenService.DebitUserCreditsAsync(user, 3);
                    _logger.LogInformation(
                        "Successfully debited credits for user {UserName} with request ID {RequestId}",
                        user.Name, creditRequestId);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to debit credits for user {UserName}. Backtest will not proceed.",
                        user.Name);
                    throw new Exception($"Failed to debit credits: {ex.Message}");
                }
            }

            try
            {
                var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
                return await RunBacktestWithCandles(config, candles, user, save, withCandles, requestId, metadata);
            }
            catch (Exception ex)
            {
                // If backtest fails and we debited credits, attempt to refund
                if (user != null && !string.IsNullOrEmpty(creditRequestId))
                {
                    try
                    {
                        var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
                        if (refundSuccess)
                        {
                            _logger.LogError(
                                "Successfully refunded credits for user {UserName} after backtest failure: {message}",
                                user.Name, ex.Message);
                        }
                        else
                        {
                            _logger.LogError("Failed to refund credits for user {UserName} after backtest failure",
                                user.Name);
                        }
                    }
                    catch (Exception refundEx)
                    {
                        _logger.LogError(refundEx, "Error during refund attempt for user {UserName}", user.Name);
                    }
                }

                throw;
            }
        }

        /// <summary>
        /// Runs a trading bot backtest with pre-loaded candles.
        /// Automatically handles different bot types based on config.BotType.
        /// </summary>
        /// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
        /// <param name="candles">The candles to use for backtesting</param>
        /// <param name="user">The user running the backtest (optional)</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 (optional)</param>
        /// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
        /// <returns>The lightweight backtest results</returns>
        public async Task<LightBacktestResponse> RunTradingBotBacktest(
            TradingBotConfig config,
            HashSet<Candle> candles,
            User user = null,
            bool withCandles = false,
            string requestId = null,
            object metadata = null)
        {
            return await RunBacktestWithCandles(config, candles, user, false, withCandles, requestId, metadata);
        }

        /// <summary>
        /// Core backtesting logic - handles the actual backtest execution with pre-loaded candles
        /// </summary>
        private async Task<LightBacktestResponse> RunBacktestWithCandles(
            TradingBotConfig config,
            HashSet<Candle> candles,
            User user = null,
            bool save = false,
            bool withCandles = false,
            string requestId = null,
            object metadata = null)
        {
            // Ensure this is a backtest configuration
            if (!config.IsForBacktest)
            {
                throw new InvalidOperationException("Backtest configuration must have IsForBacktest set to true");
            }

            // Validate that scenario and indicators are properly loaded
            if (config.Scenario == null && string.IsNullOrEmpty(config.ScenarioName))
            {
                throw new InvalidOperationException(
                    "Backtest configuration must include either Scenario object or ScenarioName");
            }

            if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
            {
                var fullScenario = await _scenarioService.GetScenarioByNameAndUserAsync(config.ScenarioName, user);
                config.Scenario = LightScenario.FromScenario(fullScenario);
            }

            // Create a clean copy of the config to avoid Orleans serialization issues
            var cleanConfig = CreateCleanConfigForOrleans(config);

            // Create Orleans grain for backtesting
            var backtestGrain = _grainFactory.GetGrain<IBacktestTradingBotGrain>(Guid.NewGuid());

            // Run the backtest using the Orleans grain and return LightBacktest directly
            return await backtestGrain.RunBacktestAsync(cleanConfig, candles, user, save, withCandles, requestId,
                metadata);
        }

        private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
        {
            return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
        }

        private HashSet<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
            DateTime startDate, DateTime endDate)
        {
            var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
                startDate, timeframe, endDate).Result;

            if (candles == null || candles.Count == 0)
                throw new Exception($"No candles for {ticker} on {timeframe} timeframe");

            return candles;
        }


        /// <summary>
        /// Creates a clean copy of the trading bot config for Orleans serialization
        /// Uses LightScenario and LightIndicator to avoid FixedSizeQueue serialization issues
        /// </summary>
        private TradingBotConfig CreateCleanConfigForOrleans(TradingBotConfig originalConfig)
        {
            // Since we're now using LightScenario in TradingBotConfig, we can just return the original config
            // The conversion to LightScenario is already done when loading the scenario
            return originalConfig;
        }

        private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
        {
            try
            {
                if (backtest.Score > 60)
                {
                    await _messengerService.SendBacktestNotification(backtest);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send backtest notification for backtest {Id}", backtest.Id);
            }
        }


        public async Task<bool> DeleteBacktestAsync(string id)
        {
            try
            {
                await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public bool DeleteBacktests()
        {
            try
            {
                _backtestRepository.DeleteAllBacktestsForUser(null);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public IEnumerable<Backtest> GetBacktestsByUser(User user)
        {
            var backtests = _backtestRepository.GetBacktestsByUser(user).ToList();
            return backtests;
        }

        public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
        {
            var backtests = await _backtestRepository.GetBacktestsByUserAsync(user);
            return backtests;
        }

        public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
        {
            var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
            return backtests;
        }

        public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
        {
            var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
            return backtests;
        }

        public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
            int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
        {
            var (backtests, totalCount) =
                _backtestRepository.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder);
            return (backtests, totalCount);
        }

        public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
            string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
        {
            var (backtests, totalCount) =
                await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
                    sortOrder);
            return (backtests, totalCount);
        }


        public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
        {
            var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);

            if (backtest == null)
                return null;

            if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
            {
                try
                {
                    var account = new Account
                        { Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };

                    var candles = await _exchangeService.GetCandlesInflux(
                        account.Exchange,
                        backtest.Config.Ticker,
                        backtest.StartDate,
                        backtest.Config.Timeframe,
                        backtest.EndDate);

                    if (candles != null && candles.Count > 0)
                    {
                        backtest.Candles = candles;
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", id);
                }
            }

            return backtest;
        }

        public async Task<bool> DeleteBacktestByUserAsync(User user, string id)
        {
            try
            {
                await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public async Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids)
        {
            try
            {
                await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to delete backtests for user {UserName}", user.Name);
                return false;
            }
        }

        public bool DeleteBacktestsByUser(User user)
        {
            try
            {
                _backtestRepository.DeleteAllBacktestsForUser(user);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
        {
            try
            {
                await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to delete backtests for request ID {RequestId}", requestId);
                return false;
            }
        }

        public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
            int pageSize, string sortBy = "score", string sortOrder = "desc")
        {
            var (backtests, totalCount) =
                _backtestRepository.GetBacktestsByUserPaginated(user, page, pageSize, sortBy, sortOrder);
            return (backtests, totalCount);
        }

        public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(
            User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
        {
            var (backtests, totalCount) =
                await _backtestRepository.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
            return (backtests, totalCount);
        }

        // Bundle backtest methods
        public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
        {
            _backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
        }

        public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
        {
            return _backtestRepository.GetBundleBacktestRequestsByUser(user);
        }

        public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
        {
            return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
        }

        public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
        {
            return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
        }

        public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
        {
            return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
        }

        public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
        {
            _backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
        }

        public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
        {
            await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
        }

        public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
        {
            _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
        }

        public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
        {
            await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
        }

        public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
        {
            // Use the repository method to get all bundles, then filter by status
            return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
        }

        public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(
            BundleBacktestRequestStatus status)
        {
            return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status);
        }

        /// <summary>
        /// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
        /// </summary>
        public async Task SendBundleBacktestUpdateAsync(string requestId, LightBacktestResponse response)
        {
            if (string.IsNullOrWhiteSpace(requestId) || response == null) return;
            await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", response);
        }
    }
}