using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Databases.PostgreSql;

public class PostgreSqlTradingRepository : ITradingRepository
{
    private readonly ManagingDbContext _context;

    public PostgreSqlTradingRepository(ManagingDbContext context)
    {
        _context = context;
    }

    #region Scenario Methods

    public async Task DeleteScenarioAsync(string name)
    {
        var scenario = await _context.Scenarios.FirstOrDefaultAsync(s => s.Name == name);
        if (scenario != null)
        {
            _context.Scenarios.Remove(scenario);
            await _context.SaveChangesAsync();
        }
    }

    public Scenario GetScenarioByName(string name)
    {
        return GetScenarioByNameAsync(name).Result;
    }

    public async Task<Scenario> GetScenarioByNameAsync(string name)
    {
        var scenario = await _context.Scenarios
            .AsNoTracking()
            .Include(s => s.User)
            .Include(s => s.ScenarioIndicators)
            .ThenInclude(si => si.Indicator)
            .FirstOrDefaultAsync(s => s.Name == name)
            .ConfigureAwait(false);

        if (scenario == null) return null;

        var mappedScenario = PostgreSqlMappers.Map(scenario);
        // Map indicators from junction table
        mappedScenario.Indicators = scenario.ScenarioIndicators
            .Select(si => PostgreSqlMappers.Map(si.Indicator))
            .ToList();

        return mappedScenario;
    }

    public IEnumerable<Scenario> GetScenarios()
    {
        return GetScenariosAsync().Result;
    }

    public async Task<IEnumerable<Scenario>> GetScenariosAsync()
    {
        var scenarios = await _context.Scenarios
            .AsNoTracking()
            .Include(s => s.ScenarioIndicators)
            .ThenInclude(si => si.Indicator)
            .ToListAsync()
            .ConfigureAwait(false);

        return scenarios.Select(scenario =>
        {
            var mappedScenario = PostgreSqlMappers.Map(scenario);
            mappedScenario.Indicators = scenario.ScenarioIndicators
                .Select(si => PostgreSqlMappers.Map(si.Indicator))
                .ToList();
            return mappedScenario;
        });
    }

    public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
    {
        var userId = user?.Id ?? 0;
        var scenarios = await _context.Scenarios
            .AsNoTracking()
            .Include(s => s.User)
            .Include(s => s.ScenarioIndicators)
            .ThenInclude(si => si.Indicator)
            .Where(s => s.UserId == userId)
            .ToListAsync()
            .ConfigureAwait(false);

        return scenarios.Select(scenario =>
        {
            var mappedScenario = PostgreSqlMappers.Map(scenario);
            mappedScenario.Indicators = scenario.ScenarioIndicators
                .Select(si => PostgreSqlMappers.Map(si.Indicator))
                .ToList();
            return mappedScenario;
        });
    }

    public async Task InsertScenarioAsync(Scenario scenario)
    {
        var userId = scenario.User?.Id ?? 0;
        
        // Check if scenario already exists for the same user
        var existingScenario = await _context.Scenarios
            .AsNoTracking()
            .FirstOrDefaultAsync(s => s.Name == scenario.Name && s.UserId == userId);

        if (existingScenario != null)
        {
            throw new InvalidOperationException(
                $"Scenario with name '{scenario.Name}' already exists for user '{scenario.User?.Name}'");
        }

        var scenarioEntity = PostgreSqlMappers.Map(scenario);
        _context.Scenarios.Add(scenarioEntity);
        await _context.SaveChangesAsync();

        // Handle scenario-indicator relationships
        if (scenario.Indicators != null && scenario.Indicators.Any())
        {
            foreach (var indicator in scenario.Indicators)
            {
                var indicatorUserId = indicator.User?.Id ?? 0;
                var indicatorEntity = await _context.Indicators
                    .AsNoTracking()
                    .FirstOrDefaultAsync(i => i.Name == indicator.Name && i.UserId == indicatorUserId);

                if (indicatorEntity != null)
                {
                    var junction = new ScenarioIndicatorEntity
                    {
                        ScenarioId = scenarioEntity.Id,
                        IndicatorId = indicatorEntity.Id
                    };
                    _context.ScenarioIndicators.Add(junction);
                }
            }

            await _context.SaveChangesAsync();
        }
    }

    public async Task UpdateScenarioAsync(Scenario scenario)
    {
        var entity = _context.Scenarios
            .AsTracking()
            .FirstOrDefault(s => s.Name == scenario.Name);

        if (entity != null)
        {
            entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1;
            entity.UserId = scenario.User?.Id ?? 0;
            entity.UpdatedAt = DateTime.UtcNow;

            await _context.SaveChangesAsync();
        }
    }

    #endregion

    #region Indicator Methods

    public async Task DeleteIndicatorAsync(string name)
    {
        var indicator = _context.Indicators
            .AsTracking()
            .FirstOrDefault(i => i.Name == name);

        if (indicator != null)
        {
            _context.Indicators.Remove(indicator);
            await _context.SaveChangesAsync();
        }
    }

    public async Task DeleteIndicatorsAsync()
    {
        var indicators = _context.Indicators.AsTracking().ToList();
        _context.Indicators.RemoveRange(indicators);
        await _context.SaveChangesAsync();
    }

    public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync()
    {
        var indicators = await _context.Indicators
            .AsNoTracking()
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(indicators);
    }

    public async Task<IEnumerable<IndicatorBase>> GetStrategiesAsync()
    {
        var indicators = await _context.Indicators
            .AsNoTracking()
            .ToListAsync()
            .ConfigureAwait(false);
        return PostgreSqlMappers.Map(indicators);
    }

    public async Task<IndicatorBase> GetStrategyByNameAsync(string name)
    {
        var indicator = await _context.Indicators
            .AsNoTracking()
            .FirstOrDefaultAsync(i => i.Name == name)
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(indicator);
    }

    public async Task InsertIndicatorAsync(IndicatorBase indicatorBase)
    {
        var indicatorUserId = indicatorBase.User?.Id ?? 0;
        // Check if indicator already exists for the same user
        var existingIndicator = await _context.Indicators
            .AsNoTracking()
            .FirstOrDefaultAsync(i => i.Name == indicatorBase.Name && i.UserId == indicatorUserId);

        if (existingIndicator != null)
        {
            throw new InvalidOperationException(
                $"Indicator with name '{indicatorBase.Name}' already exists for user '{indicatorBase.User?.Name}'");
        }

        var entity = PostgreSqlMappers.Map(indicatorBase);
        _context.Indicators.Add(entity);
        await _context.SaveChangesAsync();
    }

    public async Task UpdateStrategyAsync(IndicatorBase indicatorBase)
    {
        var entity = _context.Indicators
            .AsTracking()
            .FirstOrDefault(i => i.Name == indicatorBase.Name);

        if (entity != null)
        {
            entity.Type = indicatorBase.Type;
            entity.SignalType = indicatorBase.SignalType;
            entity.MinimumHistory = indicatorBase.MinimumHistory;
            entity.Period = indicatorBase.Period;
            entity.FastPeriods = indicatorBase.FastPeriods;
            entity.SlowPeriods = indicatorBase.SlowPeriods;
            entity.SignalPeriods = indicatorBase.SignalPeriods;
            entity.Multiplier = indicatorBase.Multiplier;
            entity.SmoothPeriods = indicatorBase.SmoothPeriods;
            entity.StochPeriods = indicatorBase.StochPeriods;
            entity.CyclePeriods = indicatorBase.CyclePeriods;
            entity.UserId = indicatorBase.User?.Id ?? 0;
            entity.UpdatedAt = DateTime.UtcNow;

            await _context.SaveChangesAsync();
        }
    }

    #endregion


    #region Position Methods

    public async Task<Position> GetPositionByIdentifierAsync(Guid identifier)
    {
        try
        {
            await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
            
            var position = await _context.Positions
                .AsNoTracking()
                .Include(p => p.User)
                .Include(p => p.OpenTrade)
                .Include(p => p.StopLossTrade)
                .Include(p => p.TakeProfit1Trade)
                .Include(p => p.TakeProfit2Trade)
                .FirstOrDefaultAsync(p => p.Identifier == identifier)
                .ConfigureAwait(false);

            return PostgreSqlMappers.Map(position);
        }
        finally
        {
            await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
        }
    }

    public IEnumerable<Position> GetPositions(PositionInitiator positionInitiator)
    {
        return GetPositionsAsync(positionInitiator).Result;
    }

    public async Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator)
    {
        var positions = await _context.Positions
            .AsNoTracking()
            .Include(p => p.User)
            .Include(p => p.OpenTrade)
            .Include(p => p.StopLossTrade)
            .Include(p => p.TakeProfit1Trade)
            .Include(p => p.TakeProfit2Trade)
            .Where(p => p.Initiator == positionInitiator)
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(positions);
    }

    public IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus)
    {
        return GetPositionsByStatusAsync(positionStatus).Result;
    }

    public async Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus)
    {
        var positions = await _context.Positions
            .AsNoTracking()
            .Include(p => p.User)
            .Include(p => p.OpenTrade)
            .Include(p => p.StopLossTrade)
            .Include(p => p.TakeProfit1Trade)
            .Include(p => p.TakeProfit2Trade)
            .Where(p => p.Status == positionStatus)
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(positions);
    }

    public async Task InsertPositionAsync(Position position)
    {
        var positionUserId = position.User?.Id ?? 0;
        // Check if position already exists for the same user
        var existingPosition = await _context.Positions
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Identifier == position.Identifier && p.UserId == positionUserId);

        if (existingPosition != null)
        {
            throw new InvalidOperationException(
                $"Position with identifier '{position.Identifier}' already exists for user '{position.User?.Name}'");
        }

        var entity = PostgreSqlMappers.Map(position);

        // Handle related trades
        if (position.Open != null)
        {
            var openTrade = PostgreSqlMappers.Map(position.Open);
            _context.Trades.Add(openTrade);
            await _context.SaveChangesAsync();
            entity.OpenTradeId = openTrade.Id;
        }

        if (position.StopLoss != null)
        {
            var stopLossTrade = PostgreSqlMappers.Map(position.StopLoss);
            _context.Trades.Add(stopLossTrade);
            await _context.SaveChangesAsync();
            entity.StopLossTradeId = stopLossTrade.Id;
        }

        if (position.TakeProfit1 != null)
        {
            var takeProfit1Trade = PostgreSqlMappers.Map(position.TakeProfit1);
            _context.Trades.Add(takeProfit1Trade);
            await _context.SaveChangesAsync();
            entity.TakeProfit1TradeId = takeProfit1Trade.Id;
        }

        if (position.TakeProfit2 != null)
        {
            var takeProfit2Trade = PostgreSqlMappers.Map(position.TakeProfit2);
            _context.Trades.Add(takeProfit2Trade);
            await _context.SaveChangesAsync();
            entity.TakeProfit2TradeId = takeProfit2Trade.Id;
        }

        _context.Positions.Add(entity);
        await _context.SaveChangesAsync();
    }

    public async Task UpdatePositionAsync(Position position)
    {
        var entity = _context.Positions
            .AsTracking()
            .FirstOrDefault(p => p.Identifier == position.Identifier);

        if (entity != null)
        {
            entity.Date = position.Date;
            entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0;
            entity.Status = position.Status;
            entity.SignalIdentifier = position.SignalIdentifier;
            entity.MoneyManagementJson = position.MoneyManagement != null
                ? JsonConvert.SerializeObject(position.MoneyManagement)
                : null;
            entity.UpdatedAt = DateTime.UtcNow;

            await _context.SaveChangesAsync();
        }
    }

    public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier)
    {
        var positions = await _context.Positions
            .AsNoTracking()
            .Include(p => p.User)
            .Include(p => p.OpenTrade)
            .Include(p => p.StopLossTrade)
            .Include(p => p.TakeProfit1Trade)
            .Include(p => p.TakeProfit2Trade)
            .Where(p => p.InitiatorIdentifier == initiatorIdentifier)
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(positions);
    }

    public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers)
    {
        var identifiersList = initiatorIdentifiers.ToList();
        if (!identifiersList.Any())
        {
            return Enumerable.Empty<Position>();
        }

        var positions = await _context.Positions
            .AsNoTracking()
            .Include(p => p.User)
            .Include(p => p.OpenTrade)
            .Include(p => p.StopLossTrade)
            .Include(p => p.TakeProfit1Trade)
            .Include(p => p.TakeProfit2Trade)
            .Where(p => identifiersList.Contains(p.InitiatorIdentifier))
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(positions);
    }

    public async Task<IEnumerable<Position>> GetAllPositionsAsync()
    {
        var positions = await _context.Positions
            .AsNoTracking()
            .Include(p => p.User)
            .Include(p => p.OpenTrade)
            .Include(p => p.StopLossTrade)
            .Include(p => p.TakeProfit1Trade)
            .Include(p => p.TakeProfit2Trade)
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(positions);
    }

    #endregion

    #region Signal Methods

    public IEnumerable<Signal> GetSignalsByUser(User user)
    {
        return GetSignalsByUserAsync(user).Result;
    }

    public async Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user)
    {
        var userId = user?.Id ?? 0;
        var signals = await _context.Signals
            .AsNoTracking()
            .Include(s => s.User)
            .Where(s => s.UserId == userId)
            .ToListAsync()
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(signals);
    }

    public Signal GetSignalByIdentifier(string identifier, User user = null)
    {
        return GetSignalByIdentifierAsync(identifier, user).Result;
    }

    public async Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null)
    {
        var userId = user?.Id ?? 0;
        var signal = await _context.Signals
            .AsNoTracking()
            .Include(s => s.User)
            .FirstOrDefaultAsync(s => s.Identifier == identifier && s.UserId == userId)
            .ConfigureAwait(false);

        return PostgreSqlMappers.Map(signal);
    }

    public async Task InsertSignalAsync(Signal signal)
    {
        var signalUserId = signal.User?.Id ?? 0;
        // Check if signal already exists with the same identifier, date, and user
        var existingSignal = _context.Signals
            .AsNoTracking()
            .FirstOrDefault(s => s.Identifier == signal.Identifier &&
                                 s.Date == signal.Date &&
                                 s.UserId == signalUserId);

        if (existingSignal != null)
        {
            throw new InvalidOperationException(
                $"Signal with identifier '{signal.Identifier}' and date '{signal.Date}' already exists for this user");
        }

        var entity = PostgreSqlMappers.Map(signal);
        _context.Signals.Add(entity);
        await _context.SaveChangesAsync();
    }

    public async Task<IndicatorBase> GetStrategyByNameUserAsync(string name, User user)
    {
        var userId = user?.Id ?? 0;
        var indicator = await _context.Indicators
            .AsNoTracking()
            .Include(i => i.User)
            .FirstOrDefaultAsync(i => i.Name == name && i.UserId == userId);
        return PostgreSqlMappers.Map(indicator);
    }

    public async Task<Scenario> GetScenarioByNameUserAsync(string scenarioName, User user)
    {
        var userId = user?.Id ?? 0;
        var scenario = await _context.Scenarios
            .AsNoTracking()
            .Include(s => s.User)
            .FirstOrDefaultAsync(s => s.Name == scenarioName && s.UserId == userId);
        return PostgreSqlMappers.Map(scenario);
    }

    #endregion
}