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.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
using MongoDB.Driver;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Databases;

public class TradingRepository : ITradingRepository
{
    private readonly IMongoRepository<ScenarioDto> _scenarioRepository;
    private readonly IMongoRepository<SignalDto> _signalRepository;
    private readonly IMongoRepository<PositionDto> _positionRepository;
    private readonly IMongoRepository<StrategyDto> _strategyRepository;
    private readonly IMongoRepository<FeeDto> _feeRepository;

    public TradingRepository(
        IMongoRepository<ScenarioDto> scenarioRepository,
        IMongoRepository<SignalDto> signalRepository,
        IMongoRepository<PositionDto> positionRepository,
        IMongoRepository<StrategyDto> strategyRepository,
        IMongoRepository<FeeDto> feeRepository)
    {
        _scenarioRepository = scenarioRepository;
        _signalRepository = signalRepository;
        _positionRepository = positionRepository;
        _strategyRepository = strategyRepository;
        _feeRepository = feeRepository;
    }

    public void DeleteScenario(string name)
    {
        var scenario = _scenarioRepository.FindOne(s => s.Name == name);
        _scenarioRepository.DeleteById(scenario.Id.ToString());
    }

    public void DeleteScenarios()
    {
        _scenarioRepository.DropCollection();
    }

    public void DeleteStrategies()
    {
        _strategyRepository.DropCollection();
    }

    public void DeleteStrategy(string name)
    {
        var strategy = _strategyRepository.FindOne(s => s.Name == name);
        _strategyRepository.DeleteById(strategy.Id.ToString());
    }

    public Position GetPositionByIdentifier(string identifier)
    {
        var position = _positionRepository.FindOne(p => p.Identifier == identifier);
        return MongoMappers.Map(position);
    }

    public IEnumerable<Position> GetPositions(PositionInitiator positionInitiator)
    {
        var filter = Builders<PositionDto>.Filter.Eq(p => p.Initiator, positionInitiator);
        var positions = _positionRepository.FilterBy(filter);
        return positions.Select(MongoMappers.Map);
    }

    public IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus)
    {
        var filter = Builders<PositionDto>.Filter.Eq(p => p.Status, positionStatus);
        var positions = _positionRepository.FilterBy(filter);
        return positions.Select(MongoMappers.Map);
    }

    public Scenario GetScenarioByName(string name)
    {
        var scenario = _scenarioRepository.FindOne(s => s.Name == name);
        return MongoMappers.Map(scenario);
    }

    public IEnumerable<Scenario> GetScenarios()
    {
        var scenarios = _scenarioRepository.FindAll();
        return scenarios.Select(s => MongoMappers.Map(s));
    }

    public IEnumerable<Strategy> GetStrategies()
    {
        var strategies = _strategyRepository.FindAll();
        return strategies.Select(MongoMappers.Map);
    }

    public Strategy GetStrategyByName(string name)
    {
        var strategy = _strategyRepository.FindOne(s => s.Name == name);
        return MongoMappers.Map(strategy);
    }

    public void InsertPosition(Position position)
    {
        // Check if position already exists for the same user
        var existingPosition = _positionRepository.FindOne(p =>
            p.Identifier == position.Identifier &&
            (position.User == null || (p.User != null && p.User.Name == position.User.Name)));

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

        var dto = MongoMappers.Map(position);
        _positionRepository.InsertOne(dto);
    }

    public void InsertScenario(Scenario scenario)
    {
        // Check if scenario already exists for the same user
        var existingScenario = _scenarioRepository.FindOne(s =>
            s.Name == scenario.Name &&
            (scenario.User == null || (s.User != null && s.User.Name == scenario.User.Name)));

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

        var strategyDtos = new List<StrategyDto>();
        foreach (var strategy in scenario.Strategies)
        {
            var dto = _strategyRepository.FindOne(s => s.Name == strategy.Name);
            strategyDtos.Add(dto);
        }

        var scenarioDto = new ScenarioDto
        {
            Name = scenario.Name,
            Strategies = strategyDtos,
            User = scenario.User != null ? MongoMappers.Map(scenario.User) : null
        };

        _scenarioRepository.InsertOne(scenarioDto);
    }

    public void InsertSignal(Signal signal)
    {
        // Check if signal already exists with the same identifier, date, and user
        var existingSignal = _signalRepository.FindOne(s =>
            s.Identifier == signal.Identifier &&
            s.Date == signal.Date &&
            ((s.User == null && signal.User == null) ||
             (s.User != null && signal.User != null && s.User.Name == signal.User.Name)));

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

        var dto = MongoMappers.Map(signal);
        _signalRepository.InsertOne(dto);
    }

    public void InsertStrategy(Strategy strategy)
    {
        // Check if strategy already exists for the same user
        var existingStrategy = _strategyRepository.FindOne(s =>
            s.Name == strategy.Name &&
            (strategy.User == null || (s.User != null && s.User.Name == strategy.User.Name)));

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

        var dto = MongoMappers.Map(strategy);
        _strategyRepository.InsertOne(dto);
    }

    public void InsertFee(Fee fee)
    {
        // Check if fee for this exchange already exists (fee is global, not user-specific)
        var existingFee = _feeRepository.FindOne(f => f.Exchange == fee.Exchange);
        if (existingFee != null)
        {
            throw new InvalidOperationException($"Fee for exchange '{fee.Exchange}' already exists");
        }

        var dto = MongoMappers.Map(fee);
        _feeRepository.InsertOne(dto);
    }

    public void UpdatePosition(Position position)
    {
        var p = _positionRepository.FindOne(p => p.Identifier.Equals(position.Identifier));
        var dto = MongoMappers.Map(position);
        dto.Id = p.Id;
        _positionRepository.Update(dto);
    }

    public Fee GetFee(TradingExchanges exchange)
    {
        var fee = _feeRepository.FindOne(f => f.Exchange == exchange);
        return MongoMappers.Map(fee);
    }

    public IEnumerable<Signal> GetSignalsByUser(User user)
    {
        IEnumerable<SignalDto> signals;

        if (user == null)
        {
            signals = _signalRepository.FilterBy(s => s.User == null);
        }
        else
        {
            signals = _signalRepository.FilterBy(s => s.User != null && s.User.Name == user.Name);
        }

        return signals.Select(MongoMappers.Map);
    }

    public Signal GetSignalByIdentifier(string identifier, User user = null)
    {
        SignalDto signal;

        if (user == null)
        {
            signal = _signalRepository.FindOne(s =>
                s.Identifier == identifier &&
                s.User == null);
        }
        else
        {
            signal = _signalRepository.FindOne(s =>
                s.Identifier == identifier &&
                s.User != null &&
                s.User.Name == user.Name);
        }

        return MongoMappers.Map(signal);
    }

    public void UpdateFee(Fee fee)
    {
        var f = _feeRepository.FindOne(f => f.Exchange == fee.Exchange);
        var dto = MongoMappers.Map(fee);
        dto.Id = f.Id;
        _feeRepository.Update(dto);
    }

    public void UpdateScenario(Scenario scenario)
    {
        var s = _scenarioRepository.FindOne(s => s.Name == scenario.Name);
        var dto = MongoMappers.Map(scenario);
        dto.Id = s.Id;
        _scenarioRepository.Update(dto);
    }

    public void UpdateStrategy(Strategy strategy)
    {
        var s = _strategyRepository.FindOne(s => s.Name == strategy.Name);
        var dto = MongoMappers.Map(strategy);
        dto.Id = s.Id;
        _strategyRepository.Update(dto);
    }
}