using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Orleans;
using Managing.Domain.Candles;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Application.Grains;

/// <summary>
/// Grain for managing platform-wide summary metrics with real-time updates and periodic snapshots
/// Uses custom trading placement with load balancing and built-in fallback.
/// </summary>
[TradingPlacement] // Use custom trading placement with load balancing
public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{
    private readonly IPersistentState<PlatformSummaryGrainState> _state;
    private readonly IBotService _botService;
    private readonly IAgentService _agentService;
    private readonly ITradingService _tradingService;
    private readonly ILogger<PlatformSummaryGrain> _logger;

    private const string _dailySnapshotReminder = "DailySnapshot";

    public PlatformSummaryGrain(
        [PersistentState("platform-summary-state", "platform-summary-store")]
        IPersistentState<PlatformSummaryGrainState> state,
        IBotService botService,
        IAgentService agentService,
        ITradingService tradingService,
        ILogger<PlatformSummaryGrain> logger)
    {
        _state = state;
        _botService = botService;
        _agentService = agentService;
        _tradingService = tradingService;
        _logger = logger;
    }

    public override async Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Platform Summary Grain activated");

        // Set up reminder for daily snapshots using precise timing
        var now = DateTime.UtcNow;

        // Daily reminder - runs at midnight (00:00 UTC)
        var nextDailyTime = CandleHelpers.GetNextExpectedCandleTime(Timeframe.OneDay, now);
        var timeUntilNextDay = nextDailyTime - now;
        
        // Ensure dueTime is never negative - if it is, schedule for next day
        if (timeUntilNextDay <= TimeSpan.Zero)
        {
            timeUntilNextDay = TimeSpan.FromDays(1).Add(TimeSpan.FromMinutes(3));
            _logger.LogWarning("Due time was negative or zero, scheduling reminder for next day instead");
        }
        
        await this.RegisterOrUpdateReminder(_dailySnapshotReminder,
            timeUntilNextDay, TimeSpan.FromDays(1).Add(TimeSpan.FromMinutes(3)));

        _logger.LogInformation("Daily reminder scheduled - Next daily: {NextDaily}, Due time: {DueTime}", 
            nextDailyTime, timeUntilNextDay);

        // Wipe daily snapshots except for the first day
        await WipeDailySnapshotsExceptFirstAsync();

        // Fill missing daily snapshots before initial data load
        await FillMissingDailySnapshotsAsync();

        // Initial data load if state is empty
        if (_state.State.LastUpdated == default)
        {
            // Create initial empty daily snapshot if none exist
            if (!_state.State.DailySnapshots.Any())
            {
                var today = DateTime.UtcNow.Date.AddSeconds(1); // Today at 00:00:01 UTC
                var initialSnapshot = new DailySnapshot
                {
                    Date = today,
                    TotalAgents = 0,
                    TotalStrategies = 0,
                    TotalVolume = 0,
                    TotalPnL = 0,
                    NetPnL = 0,
                    TotalOpenInterest = 0,
                    TotalLifetimePositionCount = 0,
                    TotalPlatformFees = 0,
                };

                _state.State.DailySnapshots.Add(initialSnapshot);
                _state.State.LastSnapshot = initialSnapshot.Date;
                _state.State.LastUpdated = initialSnapshot.Date;

                _logger.LogInformation("Created initial empty daily snapshot for {Date}", today);
            }

            await RefreshDataAsync();
        }

        await base.OnActivateAsync(cancellationToken);
    }

    public async Task<PlatformSummaryGrainState> GetPlatformSummaryAsync()
    {
        // If data is stale or has pending changes, refresh it
        if (IsDataStale() || _state.State.HasPendingChanges)
        {
            await RefreshDataAsync();
        }

        return _state.State;
    }

    public async Task RefreshDataAsync()
    {
        try
        {
            _logger.LogInformation("Refreshing platform summary data");

            var positions = await _tradingService.GetAllDatabasePositionsAsync();

            // Calculate all metrics from positions in a single loop
            var totalVolume = 0m;
            var totalFees = 0m;
            var totalPnL = 0m;
            var totalOpenInterest = 0m;
            var totalPositionCount = 0;

            // Clear state dictionaries at the start
            _state.State.VolumeByAsset.Clear();
            _state.State.PositionCountByAsset.Clear();
            _state.State.PositionCountByDirection.Clear();

            foreach (var position in positions)
            {
                // Calculate volume using the dedicated method
                var positionVolume = TradingHelpers.GetVolumeForPosition(position);
                totalVolume += positionVolume;

                // Add to open interest for active positions only (only opening volume)
                if (!position.IsFinished())
                {
                    var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
                    totalOpenInterest += openingVolume;
                }

                // Calculate fees and PnL for all positions
                totalFees += position.CalculateTotalFees();
                totalPnL += position.ProfitAndLoss?.Realized ?? 0;

                // Count all positions
                totalPositionCount++;

                // Calculate breakdown metrics and update state directly
                var ticker = position.Ticker;
                var direction = position.OriginDirection;

                // Volume breakdown by asset - update state directly
                if (!_state.State.VolumeByAsset.ContainsKey(ticker))
                {
                    _state.State.VolumeByAsset[ticker] = 0;
                }

                _state.State.VolumeByAsset[ticker] += positionVolume;

                // Position count breakdown by asset - update state directly
                if (!_state.State.PositionCountByAsset.ContainsKey(ticker))
                {
                    _state.State.PositionCountByAsset[ticker] = 0;
                }

                _state.State.PositionCountByAsset[ticker]++;

                // Position count breakdown by direction - only count finished positions
                if (!position.IsFinished())
                {
                    if (!_state.State.PositionCountByDirection.ContainsKey(direction))
                    {
                        _state.State.PositionCountByDirection[direction] = 0;
                    }

                    _state.State.PositionCountByDirection[direction]++;
                }
            }

            _state.State.TotalPlatformVolume = totalVolume;
            _state.State.TotalPlatformFees = totalFees;
            _state.State.TotalPlatformPnL = totalPnL;
            _state.State.NetPnL = totalPnL - totalFees; // Calculate NetPnL
            _state.State.OpenInterest = totalOpenInterest;
            _state.State.TotalLifetimePositionCount = totalPositionCount;
            _state.State.HasPendingChanges = false;

            _logger.LogDebug(
                "Updated position breakdown from positions: {AssetCount} assets, Long={LongPositions}, Short={ShortPositions}",
                _state.State.PositionCountByAsset.Count,
                _state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Long, 0),
                _state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Short, 0));

            _state.State.LastUpdated = DateTime.UtcNow;
            await _state.WriteStateAsync();

            _logger.LogInformation("Platform summary data refreshed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error refreshing platform summary data");
        }
    }

    // Event handlers for immediate updates
    public async Task UpdateActiveStrategyCountAsync(int newActiveCount)
    {
        try
        {
            _logger.LogInformation("Updating active strategies count to: {NewActiveCount}", newActiveCount);

            // Validate input
            if (newActiveCount < 0)
            {
                _logger.LogWarning("Invalid active strategy count: {Count}, setting to 0", newActiveCount);
                newActiveCount = 0;
            }

            _state.State.TotalActiveStrategies = newActiveCount;
            await _state.WriteStateAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error updating active strategy count to: {NewActiveCount}", newActiveCount);
        }
    }

    public async Task RefreshAgentCountAsync()
    {
        try
        {
            _state.State.TotalAgents = await _agentService.GetTotalAgentCount();
            await _state.WriteStateAsync();
            _logger.LogInformation("Agent count incremented to: {NewCount}", _state.State.TotalAgents);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error incrementing agent count");
        }
    }

    public async Task OnPositionClosedAsync(PositionClosedEvent evt)
    {
        try
        {
            _logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}",
                evt.PositionIdentifier, evt.Ticker, evt.RealizedPnL);

            _state.State.HasPendingChanges = true;
            await _state.WriteStateAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing PositionClosedEvent: {Event}", evt);
        }
    }

    public async Task OnPositionOpenAsync(PositionOpenEvent evt)
    {
        try
        {
            _logger.LogInformation("Position opened: {PositionIdentifier} for {Ticker} with volume: {Volume}",
                evt.PositionIdentifier, evt.Ticker, evt.Volume);

            _state.State.HasPendingChanges = true;
            await _state.WriteStateAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing TradeExecutedEvent: {Event}", evt);
        }
    }

    // Reminder handlers for periodic snapshots
    public async Task ReceiveReminder(string reminderName, TickStatus status)
    {
        _logger.LogInformation("Reminder received: {ReminderName}", reminderName);

        switch (reminderName)
        {
            case _dailySnapshotReminder:
                await TakeDailySnapshotAsync();
                break;
        }
    }

    private async Task TakeDailySnapshotAsync()
    {
        _logger.LogInformation("Taking daily snapshot");

        // Add daily snapshot
        var dailySnapshot = new DailySnapshot
        {
            Date = DateTime.UtcNow.Date,
            TotalAgents = _state.State.TotalAgents,
            TotalStrategies = _state.State.TotalActiveStrategies,
            TotalVolume = _state.State.TotalPlatformVolume,
            TotalPnL = _state.State.TotalPlatformPnL,
            NetPnL = _state.State.NetPnL,
            TotalOpenInterest = _state.State.OpenInterest,
            TotalLifetimePositionCount = _state.State.TotalLifetimePositionCount,
        };

        _state.State.DailySnapshots.Add(dailySnapshot);

        // Keep only last 60 days
        var cutoff = DateTime.UtcNow.AddDays(-60);
        _state.State.DailySnapshots.RemoveAll(s => s.Date < cutoff);

        _state.State.LastSnapshot = DateTime.UtcNow;

        // Reset the volume updated by events flag daily to allow periodic refresh from strategies
        _state.State.VolumeUpdatedByEvents = false;

        await _state.WriteStateAsync();
    }

    private bool IsDataStale()
    {
        var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated;
        return timeSinceLastUpdate > TimeSpan.FromMinutes(5);
    }

    /// <summary>
    /// Wipes all daily snapshots except for the first day
    /// </summary>
    private async Task WipeDailySnapshotsExceptFirstAsync()
    {
        try
        {
            if (!_state.State.DailySnapshots.Any())
            {
                _logger.LogInformation("No daily snapshots to wipe");
                return;
            }

            var originalCount = _state.State.DailySnapshots.Count;
            
            // Keep only the first day snapshot
            var firstSnapshot = _state.State.DailySnapshots.OrderBy(s => s.Date).First();
            _state.State.DailySnapshots.Clear();
            _state.State.DailySnapshots.Add(firstSnapshot);

            // Update last snapshot date to the first snapshot date
            _state.State.LastSnapshot = firstSnapshot.Date;

            await _state.WriteStateAsync();
            
            _logger.LogInformation("Wiped {WipedCount} daily snapshots, kept first snapshot from {FirstDate}", 
                originalCount - 1, firstSnapshot.Date);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error wiping daily snapshots except first");
        }
    }

    /// <summary>
    /// Fills missing daily snapshots by recalculating them from position data
    /// </summary>
    private async Task FillMissingDailySnapshotsAsync()
    {
        try
        {
            _logger.LogInformation("Checking for missing daily snapshots");

            // Get all positions to calculate missing snapshots
            var positions = await _tradingService.GetAllDatabasePositionsAsync();
            
            if (!positions.Any())
            {
                _logger.LogInformation("No positions found, skipping gap filling");
                return;
            }

            // Find the date range we need to cover
            var earliestPositionDate = positions.Min(p => p.Date).Date;
            var latestPositionDate = positions.Max(p => p.Date).Date;
            var today = DateTime.UtcNow.Date;

            // Determine the start date for gap filling
            var startDate = _state.State.DailySnapshots.Any() 
                ? _state.State.DailySnapshots.Max(s => s.Date).AddDays(1)
                : earliestPositionDate;

            // Don't go beyond today
            var endDate = today > latestPositionDate ? today : latestPositionDate;

            _logger.LogInformation("Gap filling from {StartDate} to {EndDate}", startDate, endDate);

            var missingDates = new List<DateTime>();
            for (var date = startDate; date <= endDate; date = date.AddDays(1))
            {
                if (!_state.State.DailySnapshots.Any(s => s.Date.Date == date))
                {
                    missingDates.Add(date);
                }
            }

            if (!missingDates.Any())
            {
                _logger.LogInformation("No missing daily snapshots found");
                return;
            }

            _logger.LogInformation("Found {Count} missing daily snapshots to fill", missingDates.Count);

            // Calculate and add missing snapshots
            foreach (var missingDate in missingDates)
            {
                var snapshot = await CalculateDailySnapshotFromPositionsAsync(positions.ToList(), missingDate);
                _state.State.DailySnapshots.Add(snapshot);
                
                _logger.LogInformation("Created missing daily snapshot for {Date}: Volume={Volume}, PnL={PnL}, Positions={Positions}", 
                    missingDate, snapshot.TotalVolume, snapshot.TotalPnL, snapshot.TotalLifetimePositionCount);
            }

            // Sort snapshots by date
            _state.State.DailySnapshots.Sort((a, b) => a.Date.CompareTo(b.Date));

            // Update last snapshot date
            if (_state.State.DailySnapshots.Any())
            {
                _state.State.LastSnapshot = _state.State.DailySnapshots.Max(s => s.Date);
            }

            await _state.WriteStateAsync();
            _logger.LogInformation("Successfully filled {Count} missing daily snapshots", missingDates.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error filling missing daily snapshots");
        }
    }

    /// <summary>
    /// Calculates a CUMULATIVE daily snapshot from positions up to a specific date
    /// </summary>
    /// <param name="positions">All positions to analyze</param>
    /// <param name="targetDate">The date to calculate the snapshot up to</param>
    /// <returns>A cumulative daily snapshot for the specified date</returns>
    private async Task<DailySnapshot> CalculateDailySnapshotFromPositionsAsync(List<Position> positions, DateTime targetDate)
    {
        var dayStart = targetDate;
        var dayEnd = targetDate.AddDays(1);

        // For CUMULATIVE daily snapshots, we need to consider ALL positions to calculate:
        // 1. TOTAL volume from all trades that occurred on or before this day
        // 2. Open interest from positions that were active during this day
        // 3. Cumulative position count up to this date
        // So we'll process all positions and include relevant data cumulatively

        // Calculate metrics for this specific day
        var totalVolume = 0m;
        var totalFees = 0m;
        var totalPnL = 0m;
        var maxOpenInterest = 0m;
        var totalPositionCount = 0;

        // Calculate open interest at different points during the day to find the maximum
        var hourlyOpenInterest = new List<decimal>();
        
        // Check open interest at each hour of the day (0-23)
        for (int hour = 0; hour < 24; hour++)
        {
            var hourDateTime = targetDate.AddHours(hour);
            var hourlyOI = 0m;

            foreach (var position in positions)
            {
                // Check if position was active at this hour
                var wasActiveAtThisHour = position.Date <= hourDateTime && 
                    (!position.IsFinished() || 
                     (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date > hourDateTime) ||
                     (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date > hourDateTime) ||
                     (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date > hourDateTime));

                if (wasActiveAtThisHour)
                {
                    // For open interest, only count the opening volume (not closing trades)
                    var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
                    hourlyOI += openingVolume;
                }
            }
            
            hourlyOpenInterest.Add(hourlyOI);
        }

        // Find the maximum open interest during the day
        maxOpenInterest = hourlyOpenInterest.Max();

        foreach (var position in positions)
        {
            // Calculate CUMULATIVE volume up to this point in time
            // Include all positions that were opened on or before the target date
            _logger.LogDebug("Checking position {PositionId}: Position.Date={PositionDate}, TargetDate={TargetDate}, Position.Date.Date={PositionDateOnly}", 
                position.Identifier, position.Date, targetDate, position.Date.Date);
            
            // Add opening volume if position was opened on or before this day
            // Use more flexible date comparison to handle timezone differences
            if (position.Date.Date <= targetDate)
            {
                var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
                totalVolume += openingVolume;
                _logger.LogDebug("Position {PositionId} opened on/before {TargetDate}: Opening volume = {OpeningVolume}", 
                    position.Identifier, targetDate, openingVolume);
            }

            // Add closing volume if position was closed on or before this day
            if (position.IsFinished())
            {
                if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate)
                {
                    var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
                    totalVolume += closingVolume;
                    _logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via StopLoss: Closing volume = {ClosingVolume}", 
                        position.Identifier, targetDate, closingVolume);
                }
                if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate)
                {
                    var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
                    totalVolume += closingVolume;
                    _logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}", 
                        position.Identifier, targetDate, closingVolume);
                }
                if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
                {
                    var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
                    totalVolume += closingVolume;
                }
            }

            // Calculate CUMULATIVE fees and PnL for positions closed on or before this day
            var wasClosedOnOrBeforeThisDay = position.IsFinished() && (
                (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate) ||
                (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate) ||
                (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
            );

            if (wasClosedOnOrBeforeThisDay)
            {
                totalFees += position.CalculateTotalFees();
                totalPnL += position.ProfitAndLoss?.Realized ?? 0;
            }

            // Count positions that were created on or before this day (CUMULATIVE position count)
            if (position.Date.Date <= targetDate)
            {
                totalPositionCount++;
            }
        }

        // Get current agent and strategy counts (these are current state, not historical)
        var totalAgents = await _agentService.GetTotalAgentCount();
        var totalStrategies = _state.State.TotalActiveStrategies;

        _logger.LogInformation("Calculated CUMULATIVE daily snapshot for {TargetDate}: CumVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, CumPositionCount={TotalPositionCount}", 
            targetDate, totalVolume, maxOpenInterest, totalPositionCount);

        return new DailySnapshot
        {
            Date = targetDate,
            TotalAgents = totalAgents,
            TotalStrategies = totalStrategies,
            TotalVolume = totalVolume,
            TotalPnL = totalPnL,
            NetPnL = totalPnL - totalFees,
            TotalOpenInterest = maxOpenInterest,
            TotalLifetimePositionCount = totalPositionCount,
            TotalPlatformFees = (int)totalFees,
        };
    }
}