using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Orleans;
using Managing.Application.Shared;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Microsoft.Extensions.Logging;
using Orleans.Streams;
using static Managing.Common.Enums;

namespace Managing.Application.Grains;

/// <summary>
/// Grain for fetching price data from external APIs and publishing to Orleans streams.
/// This grain runs periodically and processes all exchange/ticker combinations for a specific timeframe.
/// The timeframe is passed as the PrimaryKeyString to identify which timeframe this grain handles.
/// Uses custom trading placement with load balancing and built-in fallback.
/// </summary>
[TradingPlacement] // Use custom trading placement with load balancing
public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
{
    private readonly ILogger<PriceFetcherGrain> _logger;
    private readonly IExchangeService _exchangeService;
    private readonly ICandleRepository _candleRepository;
    private readonly IGrainFactory _grainFactory;

    private string FetchPricesReminderName => $"Fetch{TargetTimeframe}PricesReminder";

    // Predefined lists of trading parameters to fetch
    private static readonly TradingExchanges[] SupportedExchanges =
    {
        TradingExchanges.Evm
    };

    private static readonly Ticker[] SupportedTickers = Constants.GMX.Config.SupportedTickers;

    private Timeframe TargetTimeframe { get; set; }

    public PriceFetcherGrain(
        ILogger<PriceFetcherGrain> logger,
        IExchangeService exchangeService,
        ICandleRepository candleRepository,
        IGrainFactory grainFactory)
    {
        _logger = logger;
        _exchangeService = exchangeService;
        _candleRepository = candleRepository;
        _grainFactory = grainFactory;
    }

    public override async Task OnActivateAsync(CancellationToken cancellationToken)
    {
        // Parse timeframe from PrimaryKeyString
        var timeframeString = this.GetPrimaryKeyString();
        if (!Enum.TryParse<Timeframe>(timeframeString, true, out var timeframe))
        {
            _logger.LogError("Invalid timeframe '{TimeframeString}' provided as PrimaryKeyString", timeframeString);
            throw new ArgumentException($"Invalid timeframe: {timeframeString}", nameof(timeframeString));
        }

        TargetTimeframe = timeframe;

        _logger.LogInformation("{0} activated for timeframe {1}", nameof(PriceFetcherGrain), TargetTimeframe);

        // Register a reminder aligned to timeframe boundaries
        var intervalMinutes = GrainHelpers.GetIntervalMinutes(TargetTimeframe);
        var now = DateTime.UtcNow;
        var dueTime = CandleHelpers.GetDueTimeForTimeframe(TargetTimeframe, now);
        await this.RegisterOrUpdateReminder(
            FetchPricesReminderName,
            dueTime,
            TimeSpan.FromMinutes(intervalMinutes));

        await base.OnActivateAsync(cancellationToken);
    }

    public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)
    {
        _logger.LogInformation("{0} deactivating for timeframe {1}. Reason: {Reason}",
            nameof(PriceFetcherGrain), TargetTimeframe, reason.Description);

        await base.OnDeactivateAsync(reason, cancellationToken);
    }

    public async Task FetchAndPublishPricesAsync()
    {
        try
        {
            var fetchTasks = new List<Task>();

            // Create fetch tasks for all exchange/ticker combinations for the target timeframe
            foreach (var exchange in SupportedExchanges)
            {
                foreach (var ticker in SupportedTickers)
                {
                    fetchTasks.Add(FetchAndPublish(exchange, ticker, TargetTimeframe));
                }
            }

            // Execute all fetch operations in parallel
            await Task.WhenAll(fetchTasks);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during price fetch cycle for timeframe {Timeframe}", TargetTimeframe);
            SentrySdk.CaptureException(ex);
        }
    }

    private async Task FetchAndPublish(TradingExchanges exchange, Ticker ticker, Timeframe timeframe)
    {
        try
        {
            // Create a dummy account for API calls (this may need to be adjusted based on your implementation)
            var account = new Account
            {
                Name = "PriceFetcher",
                Exchange = exchange,
                Type = AccountType.Watch
            };

            // Get the last candle date from database
            var existingCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe,
                DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1), 1);

            var isFirstCall = !existingCandles.Any();

            var startDate = !isFirstCall
                ? existingCandles.Last().Date
                : new DateTime(2017, 1, 1);

            // Fetch new candles from external API
            var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, isFirstCall);

            if (newCandles?.Any() == true)
            {
                var streamKey = CandleHelpers.GetCandleStoreGrainKey(exchange, ticker, timeframe);

                _logger.LogDebug("Fetched {CandleCount} new candles for {StreamKey}",
                    newCandles.Count, streamKey);

                // Process all new candles
                var processedCandles = newCandles.OrderBy(c => c.Date)
                    .Where(c => c.Date <= DateTime.UtcNow) // Avoid duplicates
                    .ToList();

                foreach (var candle in processedCandles)
                {
                    // Ensure candle has correct metadata
                    candle.Exchange = exchange;
                    candle.Ticker = ticker;
                    candle.Timeframe = timeframe;
                }

                // Save all candles to database in a single batch
                if (processedCandles.Any())
                {
                    await _candleRepository.InsertCandles(processedCandles).ConfigureAwait(false);

                    _logger.LogInformation(
                        "[{Ticker}][{Exchange}][{Timeframe}] Inserted {CandleCount} new candles",
                        ticker, exchange, timeframe,
                        processedCandles.Count);
                }

                var streamProvider = this.GetStreamProvider("ManagingStreamProvider");
                var stream = streamProvider.GetStream<Candle>("Candles", streamKey);

                // Publish to stream (if needed)
                foreach (var candle in processedCandles)
                {
                    await stream.OnNextAsync(candle);
                    _logger.LogTrace("Published candle for {StreamKey} at {Date}",
                        streamKey, candle.Date);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching prices for {Exchange}-{Ticker}-{Timeframe}",
                exchange, ticker, timeframe);
            SentrySdk.CaptureException(ex);
        }
    }

    public Task ReceiveReminder(string reminderName, TickStatus status)
    {
        if (reminderName == FetchPricesReminderName)
        {
            return FetchAndPublishPricesAsync();
        }

        return Task.CompletedTask;
    }
}