using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Workflows;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Application.Workflows.Flows.Trading;

public class OpenPosition : FlowBase
{
    public override List<IFlow> Children { get; set; }
    public override List<FlowParameter> Parameters { get; set; }
    public override Guid ParentId { get; }
    public override Guid Id { get; }
    public override string Output { get; set; }
    public override string Name => "Open Position";
    public override FlowType Type => FlowType.OpenPosition;
    public override string Description => "This flow will open a position for a given signal";
    public OpenPositionParameters OpenPositionParameters { get; set; }
    public override List<FlowOutput> AcceptedInputs => new() { FlowOutput.Signal, FlowOutput.MoneyManagement };
    public override List<FlowOutput> OutputTypes => new() { FlowOutput.Position };
    private readonly IExchangeService _exchangeService;
    private readonly ICacheService _cacheService;
    private readonly IAccountService _accountService;
    private readonly ITradingService _tradingService;
    private readonly ISettingsRepository _settingsRepository;
    private readonly IMessengerService _messengerService;

    private readonly string POSITIONS_KEY = "positions";
    private readonly string ACCOUNT_KEY = "account";
    private readonly string CANDLES_KEY = "candles";
    private readonly string SIGNALS_KEY = "signals";
    private readonly string FEE_KEY = "fee";
    private readonly string WALLET_BALANCES = "wallet-balance";
    private decimal Fee { get; set; }


    public OpenPosition(
        IExchangeService exchangeService,
        ICacheService cacheService,
        IAccountService accountService,
        ITradingService tradingService)
    {
        _exchangeService = exchangeService;
        _cacheService = cacheService;
        _accountService = accountService;
        _tradingService = tradingService;
    }

    public async override Task Execute(string input)
    {
        MapParameters();
        var signal = JsonConvert.DeserializeObject<Signal>(input);
        var Candles = JsonConvert.DeserializeObject<HashSet<Candle>>(_cacheService.GetValue(CANDLES_KEY));
        var Account = JsonConvert.DeserializeObject<Account>(_cacheService.GetValue(ACCOUNT_KEY));
        var Positions = JsonConvert.DeserializeObject<List<Position>>(_cacheService.GetValue(POSITIONS_KEY));
        var Signals = JsonConvert.DeserializeObject<HashSet<Signal>>(_cacheService.GetValue(POSITIONS_KEY));

        await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account);

        _cacheService.SaveValue(POSITIONS_KEY, JsonConvert.SerializeObject(Positions));
        _cacheService.SaveValue(SIGNALS_KEY, JsonConvert.SerializeObject(Signals));

        if (Children != null && Children.Count > 0)
        {
            foreach (var child in Children)
            {
                await child.Execute(Output);
            }
        }
    }

    private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
        HashSet<Candle> candles, Account account)
    {
        // Check if a position is already open
        var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
                                                           && p.SignalIdentifier != signal.Identifier);

        var lastPrice = OpenPositionParameters.IsForBacktest
            ? candles.Last().Close
            : _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);

        // If position open
        if (openedPosition != null)
        {
            var previousSignal = signals.First(s => s.Identifier == openedPosition.SignalIdentifier);

            // Check if signal is the opposite side => flip the position
            if (openedPosition.OriginDirection == signal.Direction)
            {
                // An operation is already open for the same direction
                //await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
                signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
            }
            else
            {
                // An operation is already open for the opposite direction
                // ==> Flip the position
                if (OpenPositionParameters.FlipPosition)
                {
                    //await LogInformation("Try to flip the position because of an opposite direction signal");
                    //await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
                    positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status =
                        PositionStatus.Flipped;
                    await ExecuteOpenPosition(signal, positions, signals, candles, account);

                    //await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
                }
                else
                {
                    //await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
                    signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
                }
            }
        }
        else
        {
            if (!CanOpenPosition(signal, positions, signals, candles))
            {
                //await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
                signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
                return;
            }
            //await LogInformation($"Open position - Date:  {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");

            try
            {
                var moneyManagement =
                    await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
                var WalletBalances =
                    JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(
                        _cacheService.GetValue(WALLET_BALANCES));

                var command = new OpenPositionRequest(
                    OpenPositionParameters.AccountName,
                    moneyManagement,
                    signal.Direction,
                    signal.Ticker,
                    PositionInitiator.Bot,
                    signal.Date,
                    account.User,
                    100m, // Default bot trading balance
                    OpenPositionParameters.IsForBacktest,
                    lastPrice);

                var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
                    .Handle(command);

                if (position != null)
                {
                    if (position.Open.Status != TradeStatus.Cancelled)
                    {
                        position.SignalIdentifier = signal.Identifier;
                        positions.Add(position);
                        signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
                            SignalStatus.PositionOpen;

                        if (!OpenPositionParameters.IsForBacktest)
                        {
                            await _messengerService.SendPosition(position);
                        }

                        Output = JsonConvert.SerializeObject(position);
                        //Logger.LogInformation($"Position requested");
                    }
                    else
                    {
                        positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
                            PositionStatus.Rejected;
                        signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
                    }
                }
            }
            catch (Exception ex)
            {
                signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
                //await LogWarning($"Cannot open trade : {ex.Message}");
            }
        }
    }

    private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
        HashSet<Candle> candles)
    {
        if (positions.Count == 0)
            return true;

        var lastPosition = positions.LastOrDefault(p => p.IsFinished()
                                                        && p.SignalIdentifier != signal.Identifier
                                                        && p.ProfitAndLoss.Realized < 0
                                                        && p.OriginDirection == signal.Direction);

        if (lastPosition == null)
            return true;

        var tenCandleAgo = candles.TakeLast(10).First();
        var positionSignal = signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);

        return positionSignal.Date < tenCandleAgo.Date;
    }


    public override void MapParameters()
    {
        OpenPositionParameters = new OpenPositionParameters();
        foreach (var param in Parameters)
        {
            if (param.Name == nameof(OpenPositionParameters.AccountName))
                OpenPositionParameters.AccountName = param.Value;
            if (param.Name == nameof(OpenPositionParameters.MoneyManagementName))
                OpenPositionParameters.MoneyManagementName = param.Value;
            if (param.Name == nameof(OpenPositionParameters.FlipPosition))
                OpenPositionParameters.FlipPosition = bool.Parse(param.Value);
        }
    }
}

public class OpenPositionParameters
{
    public string MoneyManagementName { get; set; }
    public string AccountName { get; set; }
    public bool IsForBacktest { get; set; }
    public bool FlipPosition { get; set; }
}