using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Synth.Models;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Application.Trading;

public class TradingService : ITradingService
{
    private readonly ITradingRepository _tradingRepository;
    private readonly IExchangeService _exchangeService;
    private readonly IAccountService _accountService;
    private readonly ICacheService _cacheService;
    private readonly IMessengerService _messengerService;
    private readonly IStatisticRepository _statisticRepository;
    private readonly IEvmManager _evmManager;
    private readonly ILogger<TradingService> _logger;
    private readonly ISynthPredictionService _synthPredictionService;

    public TradingService(
        ITradingRepository tradingRepository,
        IExchangeService exchangeService,
        ILogger<TradingService> logger,
        IAccountService accountService,
        ICacheService cacheService,
        IMessengerService messengerService,
        IStatisticRepository statisticRepository,
        IEvmManager evmManager,
        ISynthPredictionService synthPredictionService)
    {
        _tradingRepository = tradingRepository;
        _exchangeService = exchangeService;
        _logger = logger;
        _accountService = accountService;
        _cacheService = cacheService;
        _messengerService = messengerService;
        _statisticRepository = statisticRepository;
        _evmManager = evmManager;
        _synthPredictionService = synthPredictionService;
    }

    public async Task DeleteScenarioAsync(string name)
    {
        await _tradingRepository.DeleteScenarioAsync(name);
    }

    public async Task DeleteIndicatorAsync(string name)
    {
        await _tradingRepository.DeleteIndicatorAsync(name);
    }

    public async Task<Position> GetPositionByIdentifierAsync(Guid identifier)
    {
        return await _tradingRepository.GetPositionByIdentifierAsync(identifier);
    }

    public async Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator)
    {
        return await _tradingRepository.GetPositionsAsync(positionInitiator);
    }

    public async Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus postionStatus)
    {
        return await _tradingRepository.GetPositionsByStatusAsync(postionStatus);
    }


    public async Task<Scenario> GetScenarioByNameAsync(string scenario)
    {
        return await _tradingRepository.GetScenarioByNameAsync(scenario);
    }

    public async Task<IEnumerable<Scenario>> GetScenariosAsync()
    {
        return await _tradingRepository.GetScenariosAsync();
    }

    public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
    {
        return await _tradingRepository.GetScenariosByUserAsync(user);
    }

    public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync()
    {
        return await _tradingRepository.GetStrategiesAsync();
    }

    public async Task<IndicatorBase> GetIndicatorByNameAsync(string strategy)
    {
        return await _tradingRepository.GetStrategyByNameAsync(strategy);
    }

    public async Task InsertPositionAsync(Position position)
    {
        await _tradingRepository.InsertPositionAsync(position);
    }

    public async Task InsertScenarioAsync(Scenario scenario)
    {
        await _tradingRepository.InsertScenarioAsync(scenario);
    }

    public async Task InsertIndicatorAsync(IndicatorBase indicatorBase)
    {
        await _tradingRepository.InsertIndicatorAsync(indicatorBase);
    }

    public async Task<Position> ManagePosition(Account account, Position position)
    {
        var lastPrice = await _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
        var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
        var orders = await _exchangeService.GetOpenOrders(account, position.Ticker);

        if (quantityInPosition > 0)
        {
            // Position still open
            position.ProfitAndLoss =
                TradingBox.GetProfitAndLoss(position, position.Open.Quantity, lastPrice, position.Open.Leverage);
            _logger.LogInformation($"Position is still open - PNL : {position.ProfitAndLoss.Realized} $");
            _logger.LogInformation($"Requested trades : {orders.Count}");
        }
        else
        {
            // No quantity in position = SL/TP hit 
            if (orders.All(o => o.TradeType != TradeType.StopLoss))
            {
                // SL hit
                _logger.LogInformation($"Stop loss is filled on exchange.");
                position.StopLoss.SetStatus(TradeStatus.Filled);
                position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.StopLoss.Quantity,
                    position.StopLoss.Price, position.Open.Leverage);
                _ = _exchangeService.CancelOrder(account, position.Ticker);
            }
            else if (orders.All(o => o.TradeType != TradeType.TakeProfit))
            {
                // TP Hit
                if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit2 != null)
                {
                    position.TakeProfit2.SetStatus(TradeStatus.Filled);
                    position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit2.Quantity,
                        position.TakeProfit2.Price, 1);
                    _logger.LogInformation($"TakeProfit 2 is filled on exchange.");
                }
                else
                {
                    position.TakeProfit1.SetStatus(TradeStatus.Filled);
                    position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit1.Quantity,
                        position.TakeProfit1.Price, 1);
                    _logger.LogInformation($"TakeProfit 1 is filled on exchange.");
                }
            }
            else
            {
                _logger.LogInformation(
                    $"Position closed manually or forced close by exchange because quantity in position is below 0.");
                position.Status = PositionStatus.Finished;

                if (orders.Any()) await _exchangeService.CancelOrder(account, position.Ticker);
            }
        }

        return position;
    }


    public async Task UpdatePositionAsync(Position position)
    {
        await _tradingRepository.UpdatePositionAsync(position);
    }

    public async Task<IEnumerable<Position>> GetPositionsAsync()
    {
        var positions = new List<Position>();
        positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.New));
        positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.Filled));
        positions.AddRange(await GetPositionsByStatusAsync(PositionStatus.PartiallyFilled));
        return positions;
    }


    public async Task WatchTrader()
    {
        var availableTickers = new List<Ticker> { Ticker.BTC, Ticker.ETH, Ticker.UNI, Ticker.LINK };
        var watchAccount = await GetTradersWatch();
        var key = $"AccountsQuantityInPosition";
        var aqip = _cacheService.GetValue<List<TraderFollowup>>(key);

        if (aqip == null)
        {
            aqip = GetAccountsQuantityInPosition(watchAccount);
        }
        else
        {
            foreach (var a in watchAccount.Where(w => !aqip.Any(a => a.Account.Address == w.Address)))
            {
                var newAccount = SetupFollowUp(a);
                aqip.Add(newAccount);
            }

            foreach (var a in aqip)
            {
                await ManageTrader(a, availableTickers);
            }
        }

        _cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10));
    }

    public async Task<IEnumerable<Trader>> GetTradersWatch()
    {
        var watchAccount = await _statisticRepository.GetBestTradersAsync();
        var customWatchAccount = (await _accountService.GetAccountsAsync(true, false))
            .Where(a => a.Type == AccountType.Watch)
            .ToList().MapToTraders();
        watchAccount.AddRange(customWatchAccount.Where(a =>
            !watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase))));
        return watchAccount;
    }

    public void UpdateDeltaNeutralOpportunities()
    {
        var fundingRates = _exchangeService.GetFundingRates();
    }

    public async Task UpdateScenarioAsync(Scenario scenario)
    {
        await _tradingRepository.UpdateScenarioAsync(scenario);
    }

    public async Task UpdateIndicatorAsync(IndicatorBase indicatorBase)
    {
        await _tradingRepository.UpdateStrategyAsync(indicatorBase);
    }

    public async Task<IEnumerable<Position>> GetBrokerPositions(Account account)
    {
        return await _exchangeService.GetBrokerPositions(account);
    }

    public async Task<IEnumerable<Position>> GetAllDatabasePositionsAsync()
    {
        return await _tradingRepository.GetAllPositionsAsync();
    }

    public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier)
    {
        return await _tradingRepository.GetPositionsByInitiatorIdentifierAsync(initiatorIdentifier);
    }

    public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers)
    {
        return await _tradingRepository.GetPositionsByInitiatorIdentifiersAsync(initiatorIdentifiers);
    }

    private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
    {
        var shortAddress = a.Account.Address.Substring(0, 6);

        foreach (var ticker in tickers)
        {
            try
            {
                var newTrade = await _exchangeService.GetTrade(a.Account.Address, "", ticker);
                var oldTrade = a.Trades.SingleOrDefault(t => t.Ticker == ticker);
                if (newTrade == null)
                {
                    if (oldTrade != null)
                    {
                        _logger.LogInformation(
                            $"[{shortAddress}][{ticker}] Trader previously got a position open but the position was close by trader");
                        await _messengerService.SendClosedPosition(a.Account.Address, oldTrade);
                        a.Trades.Remove(oldTrade);
                    }
                }
                else if ((newTrade != null && oldTrade == null) || (newTrade.Quantity > oldTrade.Quantity))
                {
                    _logger.LogInformation(
                        $"[{shortAddress}][{ticker}] Trader increase {newTrade.Direction} by {newTrade.Quantity - (oldTrade?.Quantity ?? 0)} with leverage {newTrade.Leverage} at {newTrade.Price} leverage.");

                    var index = a.Trades.IndexOf(oldTrade);
                    if (index != -1)
                    {
                        a.Trades[index] = newTrade;
                    }
                    else
                    {
                        a.Trades.Add(newTrade);
                    }

                    // Open position
                    await _messengerService.SendIncreasePosition(a.Account.Address, newTrade, "Test6", oldTrade);
                    // Save position to cache
                }
                else if (newTrade.Quantity < oldTrade.Quantity && newTrade.Quantity > 0)
                {
                    var decreaseAmount = oldTrade.Quantity - newTrade.Quantity;
                    var index = a.Trades.IndexOf(oldTrade);
                    a.Trades[index] = newTrade;
                    _logger.LogInformation(
                        $"[{a.Account.Address.Substring(0, 6)}][{ticker}] Trader decrease position but didnt close it {decreaseAmount}");
                    await _messengerService.SendDecreasePosition(a.Account.Address, newTrade, decreaseAmount);
                }
                else
                {
                    _logger.LogInformation(
                        $"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"[{shortAddress}][{ticker}] Impossible to fetch trader");
            }
        }
    }

    private List<TraderFollowup> GetAccountsQuantityInPosition(IEnumerable<Trader> watchAccount)
    {
        var result = new List<TraderFollowup>();
        foreach (var account in watchAccount)
        {
            var trader = SetupFollowUp(account);
            result.Add(trader);
        }

        return result;
    }

    private static TraderFollowup SetupFollowUp(Trader account)
    {
        var trader = new TraderFollowup
        {
            Account = account,
            Trades = new List<Trade>(),
            PositionIdentifiers = new List<string>()
        };

        return trader;
    }

    public class TraderFollowup
    {
        public Trader Account { get; set; }
        public List<Trade> Trades { get; set; }
        public List<string> PositionIdentifiers { get; set; }
    }

    public async Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress)
    {
        try
        {
            if (string.IsNullOrEmpty(publicAddress))
            {
                _logger.LogWarning("Attempted to initialize Privy wallet with null or empty public address");
                return new PrivyInitAddressResponse
                    { Success = false, Error = "Public address cannot be null or empty" };
            }

            return await _evmManager.InitAddress(publicAddress);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error initializing Privy wallet for address {PublicAddress}", publicAddress);
            return new PrivyInitAddressResponse { Success = false, Error = ex.Message };
        }
    }

    // Synth API integration methods
    public async Task<SignalValidationResult> ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice,
        TradingBotConfig botConfig, bool isBacktest)
    {
        return await _synthPredictionService.ValidateSignalAsync(signal, currentPrice, botConfig, isBacktest);
    }

    public async Task<bool> AssessSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
        TradingBotConfig botConfig, bool isBacktest)
    {
        return await _synthPredictionService.AssessPositionRiskAsync(ticker, direction, currentPrice,
            botConfig, isBacktest);
    }

    public async Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction,
        decimal currentPrice, decimal liquidationPrice, Guid positionIdentifier, TradingBotConfig botConfig)
    {
        return await _synthPredictionService.MonitorPositionRiskAsync(ticker, direction, currentPrice, liquidationPrice,
            positionIdentifier, botConfig);
    }

    /// <summary>
    /// Calculates indicators values for a given scenario and candles.
    /// </summary>
    /// <param name="scenario">The scenario containing indicators.</param>
    /// <param name="candles">The candles to calculate indicators for.</param>
    /// <returns>A dictionary of indicator types to their calculated values.</returns>
    public Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
        Scenario scenario,
        HashSet<Candle> candles)
    {
        var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();

        if (scenario?.Indicators == null || scenario.Indicators.Count == 0)
        {
            return indicatorsValues;
        }

        // Build indicators from scenario
        foreach (var indicator in scenario.Indicators)
        {
            try
            {
                indicatorsValues[indicator.Type] = indicator.GetIndicatorValues(candles);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}",
                    indicator.Name, ex.Message);
            }
        }

        return indicatorsValues;
    }

    public async Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user)
    {
        return await _tradingRepository.GetStrategyByNameUserAsync(name, user);
    }

    public async Task<Scenario?> GetScenarioByNameUserAsync(string scenarioName, User user)
    {
        return await _tradingRepository.GetScenarioByNameUserAsync(scenarioName, user);
    }
}