using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Managing.Domain.Workers;
using Managing.Domain.Workflows.Synthetics;
using Managing.Infrastructure.Databases.MongoDb.Collections;
using MongoDB.Bson;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Databases.MongoDb;

public static class MongoMappers
{
    #region Statistics

    internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker)
    {
        return new TopVolumeTickerDto
        {
            Ticker = topVolumeTicker.Ticker,
            Date = topVolumeTicker.Date,
            Volume = topVolumeTicker.Volume,
            Rank = topVolumeTicker.Rank,
            Exchange = topVolumeTicker.Exchange
        };
    }

    internal static IList<TopVolumeTicker> Map(IEnumerable<TopVolumeTickerDto> top)
    {
        return top.Select(topElement => new TopVolumeTicker
        {
            Ticker = topElement.Ticker,
            Date = topElement.Date,
            Volume = topElement.Volume,
            Rank = topElement.Rank,
            Exchange = topElement.Exchange
        }).ToList();
    }

    #endregion

    #region Accounts

    internal static AccountDto Map(Account request)
    {
        return new AccountDto
        {
            Name = request.Name,
            Exchanges = request.Exchange,
            Key = request.Key,
            Secret = request.Secret,
            Type = request.Type,
            User = Map(request.User)
        };
    }

    internal static IEnumerable<Account> Map(IEnumerable<AccountDto> accounts)
    {
        return accounts.Select(account => Map(account));
    }

    internal static Account Map(AccountDto account, bool hideKeys = false)
    {
        if (account == null) return null;

        var a = new Account
        {
            Name = account.Name,
            Exchange = account.Exchanges,
        };

        if (!hideKeys)
        {
            a.Key = account.Key;
            a.Secret = account.Secret;
        }

        a.Exchange = account.Exchanges;
        a.Type = account.Type;
        a.User = Map(account.User);

        return a;
    }

    #endregion

    #region Workers

    internal static WorkerDto Map(Worker worker)
    {
        return new WorkerDto
        {
            WorkerType = worker.WorkerType,
            StartTime = worker.StartTime,
            LastRunTime = worker.LastRunTime,
            ExecutionCount = worker.ExecutionCount,
            Delay = worker.Delay
        };
    }

    internal static Worker Map(WorkerDto worker)
    {
        if (worker == null)
            return null;

        return new Worker
        {
            WorkerType = worker.WorkerType,
            StartTime = worker.StartTime,
            LastRunTime = worker.LastRunTime,
            ExecutionCount = worker.ExecutionCount,
            Delay = worker.Delay,
            IsActive = worker.IsActive
        };
    }

    #endregion

    #region Backtests

    internal static Backtest Map(BacktestDto b)
    {
        if (b == null)
            return null;

        var config = new TradingBotConfig
        {
            AccountName = b.AccountName,
            Ticker = b.Ticker,
            ScenarioName = b.ScenarioName,
            Timeframe = b.Timeframe,
            IsForWatchingOnly = b.IsForWatchingOnly,
            BotTradingBalance = b.BotTradingBalance,
            BotType = b.BotType,
            IsForBacktest = b.IsForBacktest,
            CooldownPeriod = b.CooldownPeriod,
            MaxLossStreak = b.MaxLossStreak,
            MoneyManagement = Map(b.MoneyManagement)
        };

        var bTest = new Backtest(
            config,
            b.Positions?.Select(p => Map(p)).ToList() ?? new List<Position>(),
            b.Signals?.Select(s => Map(s)).ToList() ?? new List<Signal>(),
            b.Candles?.Select(c => Map(c)).ToList() ?? new List<Candle>())
        {
            FinalPnl = b.FinalPnl,
            WinRate = b.WinRate,
            GrowthPercentage = b.GrowthPercentage,
            HodlPercentage = b.HodlPercentage,
            Id = b.Id.ToString(),
            OptimizedMoneyManagement = Map(b.OptimizedMoneyManagement),
            User = Map(b.User),
            Statistics = b.Statistics,
            StartDate = b.StartDate,
            EndDate = b.EndDate,
            Score = b.Score
        };

        return bTest;
    }

    internal static BacktestDto Map(Backtest result)
    {
        if (result == null)
            return null;

        return new BacktestDto
        {
            Id = (!string.IsNullOrEmpty(result.Id)) ? ObjectId.Parse(result.Id) : ObjectId.GenerateNewId(),
            FinalPnl = result.FinalPnl,
            WinRate = result.WinRate,
            GrowthPercentage = result.GrowthPercentage,
            HodlPercentage = result.HodlPercentage,
            Positions = Map(result.Positions),
            Signals = result.Signals.Select(s => Map(s)).ToList(),
            Candles = result.Candles.Select(c => Map(c)).ToList(),
            MoneyManagement = Map(result.Config.MoneyManagement),
            OptimizedMoneyManagement = Map(result.OptimizedMoneyManagement),
            User = Map(result.User),
            Statistics = result.Statistics,
            StartDate = result.StartDate,
            EndDate = result.EndDate,
            Score = result.Score,
            AccountName = result.Config.AccountName,
            Ticker = result.Config.Ticker,
            ScenarioName = result.Config.ScenarioName,
            Timeframe = result.Config.Timeframe,
            IsForWatchingOnly = result.Config.IsForWatchingOnly,
            BotTradingBalance = result.Config.BotTradingBalance,
            BotType = result.Config.BotType,
            IsForBacktest = result.Config.IsForBacktest,
            CooldownPeriod = result.Config.CooldownPeriod,
            MaxLossStreak = result.Config.MaxLossStreak
        };
    }

    #endregion

    #region Candles

    public static Candle Map(CandleDto candle)
    {
        if (candle == null)
            return null;

        return new Candle()
        {
            Ticker = candle.Ticker,
            BaseVolume = candle.BaseVolume,
            Close = candle.Close,
            Date = candle.OpenTime,
            Open = candle.Open,
            OpenTime = candle.OpenTime,
            High = candle.High,
            Low = candle.Low,
            QuoteVolume = candle.QuoteVolume,
            TakerBuyBaseVolume = candle.TakerBuyBaseVolume,
            TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume,
            TradeCount = candle.TradeCount,
            Exchange = candle.Exchange,
        };
    }

    public static CandleDto Map(Candle candle)
    {
        return new CandleDto
        {
            Exchange = candle.Exchange,
            Ticker = candle.Ticker,
            OpenTime = candle.OpenTime,
            Open = candle.Open,
            Close = candle.Close,
            High = candle.High,
            Low = candle.Low,
            BaseVolume = candle.BaseVolume,
            QuoteVolume = candle.QuoteVolume,
            TradeCount = candle.TradeCount,
            TakerBuyBaseVolume = candle.TakerBuyBaseVolume,
            TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume
        };
    }

    public static List<CandleDto> Map(List<Candle> candles)
    {
        return candles.ConvertAll(candle => Map(candle));
    }

    #endregion

    #region Positions

    public static PositionDto Map(Position position)
    {
        var p = new PositionDto
        {
            Date = position.Date,
            Open = Map(position.Open),
            OriginDirection = position.OriginDirection,
            Identifier = position.Identifier,
            SignalIdentifier = position.SignalIdentifier,
            Status = position.Status,
            AccountName = position.AccountName,
            MoneyManagement = Map(position.MoneyManagement),
            Initiator = position.Initiator,
            Ticker = position.Ticker,
            User = Map(position.User)
        };

        if (position.StopLoss != null)
            p.StopLoss = Map(position.StopLoss);

        if (position.TakeProfit1 != null)
            p.TakeProfit1 = Map(position.TakeProfit1);

        if (position.TakeProfit2 != null)
            p.TakeProfit2 = Map(position.TakeProfit2);

        if (position.ProfitAndLoss != null)
            p.ProfitAndLoss = position.ProfitAndLoss.Realized;

        return p;
    }

    private static TradeDto Map(Trade trade)
    {
        return new TradeDto
        {
            Date = trade.Date,
            Direction = trade.Direction,
            Status = trade.Status,
            TradeType = trade.TradeType,
            Ticker = trade.Ticker,
            Quantity = trade.Quantity,
            Price = trade.Price,
            Leverage = trade.Leverage,
            ExchangeOrderId = trade.ExchangeOrderId,
            Message = trade.Message
        };
    }

    public static Position Map(PositionDto dto)
    {
        var position = new Position(dto.Identifier, dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
            Map(dto.MoneyManagement), dto.Initiator, dto.Date, Map(dto.User))
        {
            Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status,
                tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity,
                price: dto.Open.Price, leverage: dto.Open.Leverage, exchangeOrderId: dto.Open.ExchangeOrderId,
                message: dto.Open.Message),
            ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss },
            Status = dto.Status,
            SignalIdentifier = dto.SignalIdentifier,
            Identifier = dto.Identifier,
            User = Map(dto.User)
        };

        if (dto.StopLoss != null)
        {
            position.StopLoss = new Trade(date: dto.StopLoss.Date, direction: dto.StopLoss.Direction,
                status: dto.StopLoss.Status, tradeType: dto.StopLoss.TradeType, ticker: dto.StopLoss.Ticker,
                quantity: dto.StopLoss.Quantity, price: dto.StopLoss.Price, leverage: dto.StopLoss.Leverage,
                exchangeOrderId: dto.StopLoss.ExchangeOrderId, message: dto.StopLoss.Message);
        }

        if (dto.TakeProfit1 != null)
        {
            position.TakeProfit1 = new Trade(date: dto.TakeProfit1.Date, direction: dto.TakeProfit1.Direction,
                status: dto.TakeProfit1.Status, tradeType: dto.TakeProfit1.TradeType, ticker: dto.TakeProfit1.Ticker,
                quantity: dto.TakeProfit1.Quantity, price: dto.TakeProfit1.Price, leverage: dto.TakeProfit1.Leverage,
                exchangeOrderId: dto.TakeProfit1.ExchangeOrderId, message: dto.TakeProfit1.Message);
        }

        if (dto.TakeProfit2 != null)
        {
            position.TakeProfit2 = new Trade(date: dto.TakeProfit2.Date, direction: dto.TakeProfit2.Direction,
                status: dto.TakeProfit2.Status, tradeType: dto.TakeProfit2.TradeType, ticker: dto.TakeProfit2.Ticker,
                quantity: dto.TakeProfit2.Quantity, price: dto.TakeProfit2.Price, leverage: dto.TakeProfit2.Leverage,
                exchangeOrderId: dto.TakeProfit2.ExchangeOrderId, message: dto.TakeProfit2.Message);
        }

        return position;
    }

    internal static List<PositionDto> Map(List<Position> positions)
    {
        return positions.ConvertAll(position => Map(position));
    }

    #endregion

    #region Signals

    public static SignalDto Map(Signal signal)
    {
        return new SignalDto
        {
            Direction = signal.Direction,
            Confidence = signal.Confidence,
            Date = signal.Date,
            Candle = Map(signal.Candle),
            Identifier = signal.Identifier,
            Ticker = signal.Ticker,
            Status = signal.Status,
            Timeframe = signal.Timeframe,
            Type = signal.StrategyType,
            User = signal.User != null ? Map(signal.User) : null
        };
    }

    internal static Signal Map(SignalDto bSignal)
    {
        var candle = Map(bSignal.Candle);
        var signal = new Signal(
            bSignal.Ticker,
            bSignal.Direction,
            bSignal.Confidence,
            candle,
            bSignal.Date,
            TradingExchanges.Binance, //TODO FIXME When the signal status is modified from controller
            bSignal.Type,
            bSignal.SignalType,
            bSignal.User != null ? Map(bSignal.User) : null)
        {
            Status = bSignal.Status
        };

        if (bSignal.User != null)
        {
            signal.User = Map(bSignal.User);
        }

        return signal;
    }

    #endregion

    #region Scenarios

    public static ScenarioDto Map(Scenario scenario)
    {
        if (scenario == null)
            return null;

        return new ScenarioDto
        {
            Name = scenario.Name,
            Strategies = Map(scenario.Strategies),
            LoopbackPeriod = scenario.LoopbackPeriod ?? 1,
            User = scenario.User != null ? Map(scenario.User) : null
        };
    }

    internal static IEnumerable<Scenario> Map(IEnumerable<ScenarioDto> dtos)
    {
        return dtos.Select(d => Map(d));
    }

    internal static Scenario Map(ScenarioDto d)
    {
        if (d == null)
            return null;

        var scenario = new Scenario(d.Name, d.LoopbackPeriod)
        {
            Strategies = d.Strategies.Select(s => Map(s)).ToList(),
            User = d.User != null ? Map(d.User) : null
        };
        return scenario;
    }

    private static List<StrategyDto> Map(List<Strategy> strategies)
    {
        return strategies.ConvertAll(strategy => Map(strategy));
    }

    internal static Strategy Map(StrategyDto strategyDto)
    {
        if (strategyDto == null)
            return null;

        return new Strategy(strategyDto.Name, strategyDto.Type)
        {
            SignalType = strategyDto.SignalType,
            MinimumHistory = strategyDto.MinimumHistory,
            Period = strategyDto.Period,
            FastPeriods = strategyDto.FastPeriods,
            SlowPeriods = strategyDto.SlowPeriods,
            SignalPeriods = strategyDto.SignalPeriods,
            Multiplier = strategyDto.Multiplier,
            SmoothPeriods = strategyDto.SmoothPeriods,
            StochPeriods = strategyDto.StochPeriods,
            CyclePeriods = strategyDto.CyclePeriods,
            User = strategyDto.User != null ? Map(strategyDto.User) : null
        };
    }

    internal static StrategyDto Map(Strategy strategy)
    {
        if (strategy == null)
            return null;

        return new StrategyDto
        {
            Name = strategy.Name,
            Type = strategy.Type,
            SignalType = strategy.SignalType,
            MinimumHistory = strategy.MinimumHistory,
            Period = strategy.Period,
            FastPeriods = strategy.FastPeriods,
            SlowPeriods = strategy.SlowPeriods,
            SignalPeriods = strategy.SignalPeriods,
            Multiplier = strategy.Multiplier,
            SmoothPeriods = strategy.SmoothPeriods,
            StochPeriods = strategy.StochPeriods,
            CyclePeriods = strategy.CyclePeriods,
            User = strategy.User != null ? Map(strategy.User) : null
        };
    }

    internal static IEnumerable<Strategy> Map(IEnumerable<StrategyDto> strategies)
    {
        return strategies.Select(strategy => Map(strategy));
    }

    #endregion

    #region Money Management

    public static MoneyManagementDto Map(MoneyManagement request)
    {
        if (request == null) return null;
        return new MoneyManagementDto
        {
            Timeframe = request.Timeframe,
            StopLoss = request.StopLoss,
            TakeProfit = request.TakeProfit,
            Leverage = request.Leverage,
            Name = request.Name,
            User = request.User != null ? Map(request.User) : null
        };
    }

    public static MoneyManagement Map(MoneyManagementDto request)
    {
        if (request == null)
            return null;

        return new MoneyManagement
        {
            Timeframe = request.Timeframe,
            StopLoss = request.StopLoss,
            TakeProfit = request.TakeProfit,
            Leverage = request.Leverage,
            Name = request.Name,
            User = request.User != null ? Map(request.User) : null
        };
    }

    internal static User Map(UserDto user)
    {
        if (user == null)
            return null;

        return new User
        {
            Name = user.Name,
            AgentName = user.AgentName,
            AvatarUrl = user.AvatarUrl,
            TelegramChannel = user.TelegramChannel
        };
    }

    internal static UserDto Map(User user)
    {
        return new UserDto
        {
            Name = user.Name,
            AgentName = user.AgentName,
            AvatarUrl = user.AvatarUrl,
            TelegramChannel = user.TelegramChannel
        };
    }

    internal static SpotlighOverviewDto Map(SpotlightOverview overview)
    {
        return new SpotlighOverviewDto
        {
            Spotlights = Map(overview.Spotlights),
            DateTime = overview.DateTime,
            Identifier = overview.Identifier,
            ScenarioCount = overview.ScenarioCount,
        };
    }

    private static List<SpotlightDto> Map(List<Spotlight> spotlights)
    {
        return spotlights.ConvertAll(spotlight => new SpotlightDto
        {
            Scenario = new ScenarioDto
            {
                Name = spotlight.Scenario.Name,
                Strategies =
                    spotlight.Scenario.Strategies.ConvertAll(
                        spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
            },
            TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignalDto
            {
                Ticker = spotlightTickerSignal.Ticker,
                FiveMinutes =
                    spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute =>
                        Map(spotlightTickerSignalFiveMinute)) ?? new List<SignalDto>(),
                FifteenMinutes =
                    spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute =>
                        Map(spotlightTickerSignalFifteenMinute)) ?? new List<SignalDto>(),
                OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour =>
                    Map(spotlightTickerSignalOneHour)) ?? new List<SignalDto>(),
                FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour =>
                    Map(spotlightTickerSignalFourHour)) ?? new List<SignalDto>(),
                OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay =>
                    Map(spotlightTickerSignalOneDay)) ?? new List<SignalDto>()
            })
        });
    }

    internal static SpotlightOverview Map(SpotlighOverviewDto overview)
    {
        return new SpotlightOverview
        {
            Spotlights = Map(overview.Spotlights),
            DateTime = overview.DateTime,
            Identifier = overview.Identifier,
            ScenarioCount = overview.ScenarioCount
        };
    }

    private static List<Spotlight> Map(List<SpotlightDto> spotlights)
    {
        return spotlights.ConvertAll(spotlight => new Spotlight
        {
            Scenario = new Scenario(name: spotlight.Scenario.Name)
            {
                Strategies =
                    spotlight.Scenario.Strategies.ConvertAll(
                        spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
            },
            TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignal
            {
                Ticker = spotlightTickerSignal.Ticker,
                FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute =>
                    Map(spotlightTickerSignalFiveMinute)),
                FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute =>
                    Map(spotlightTickerSignalFifteenMinute)),
                OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour =>
                    Map(spotlightTickerSignalOneHour)),
                FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour =>
                    Map(spotlightTickerSignalFourHour)),
                OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay =>
                    Map(spotlightTickerSignalOneDay))
            })
        });
    }

    internal static IList<SpotlightOverview> Map(IEnumerable<SpotlighOverviewDto> overviews)
    {
        return overviews.Select(Map).ToList();
    }


    internal static FeeDto Map(Fee fee)
    {
        return new FeeDto
        {
            Cost = fee.Cost,
            Exchange = fee.Exchange,
            LastUpdate = fee.LastUpdate
        };
    }

    internal static Fee Map(FeeDto fee)
    {
        if (fee == null) return null;

        return new Fee
        {
            Cost = fee.Cost,
            Exchange = fee.Exchange,
            LastUpdate = fee.LastUpdate
        };
    }

    internal static List<Trader> Map(IEnumerable<BestTraderDto> enumerable)
    {
        return enumerable.Select(enumerableElement => new Trader
        {
            Address = enumerableElement.Address,
            Winrate = enumerableElement.Winrate,
            Pnl = enumerableElement.Pnl,
            TradeCount = enumerableElement.TradeCount,
            AverageWin = enumerableElement.AverageWin,
            AverageLoss = enumerableElement.AverageLoss,
            Roi = enumerableElement.Roi
        }).ToList();
    }

    internal static BestTraderDto Map(Trader trader)
    {
        return new BestTraderDto
        {
            Address = trader.Address,
            Winrate = trader.Winrate,
            Pnl = trader.Pnl,
            TradeCount = trader.TradeCount,
            AverageWin = trader.AverageWin,
            AverageLoss = trader.AverageLoss,
            Roi = trader.Roi
        };
    }

    internal static List<Trader> Map(IEnumerable<BadTraderDto> enumerable)
    {
        return enumerable.Select(enumerableElement => new Trader
        {
            Address = enumerableElement.Address,
            Winrate = enumerableElement.Winrate,
            Pnl = enumerableElement.Pnl,
            TradeCount = enumerableElement.TradeCount,
            AverageWin = enumerableElement.AverageWin,
            AverageLoss = enumerableElement.AverageLoss,
            Roi = enumerableElement.Roi
        }).ToList();
    }

    internal static BadTraderDto BadTraderMap(Trader trader)
    {
        return new BadTraderDto
        {
            Address = trader.Address,
            Winrate = trader.Winrate,
            Pnl = trader.Pnl,
            TradeCount = trader.TradeCount,
            AverageWin = trader.AverageWin,
            AverageLoss = trader.AverageLoss,
            Roi = trader.Roi
        };
    }

    internal static WorkflowDto Map(SyntheticWorkflow workflow)
    {
        return new WorkflowDto
        {
            Name = workflow.Name,
            Description = workflow.Description,
            Usage = workflow.Usage,
            Flows = workflow.Flows
        };
    }

    internal static SyntheticWorkflow Map(WorkflowDto m)
    {
        if (m == null) return null;

        return new SyntheticWorkflow
        {
            Name = m.Name,
            Usage = m.Usage,
            Description = m.Description,
            Flows = m.Flows.ToList(),
        };
    }

    internal static BotDto Map(BotBackup bot)
    {
        if (bot == null) return null;

        return new BotDto
        {
            User = Map(bot.User),
            Identifier = bot.Identifier,
            BotType = bot.BotType,
            Data = bot.Data,
            LastStatus = bot.LastStatus
        };
    }

    internal static BotBackup Map(BotDto b)
    {
        if (b == null) return null;

        return new BotBackup
        {
            User = Map(b.User),
            Identifier = b.Identifier,
            BotType = b.BotType,
            Data = b.Data,
            LastStatus = b.LastStatus
        };
    }

    #endregion

    public static FundingRate Map(FundingRateDto fundingRate)
    {
        if (fundingRate == null)
            return null;

        return new FundingRate
        {
            Exchange = fundingRate.Exchange,
            Rate = fundingRate.Rate,
            Ticker = fundingRate.Ticker,
            Date = fundingRate.Date,
            Direction = fundingRate.Direction
        };
    }

    public static FundingRateDto Map(FundingRate fundingRate)
    {
        return new FundingRateDto
        {
            Exchange = fundingRate.Exchange,
            Rate = fundingRate.Rate,
            Ticker = fundingRate.Ticker,
            Date = fundingRate.Date,
            Direction = fundingRate.Direction
        };
    }
}