using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Statistics;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Database.PostgreSql;

public class AgentSummaryRepository : IAgentSummaryRepository
{
    private readonly ManagingDbContext _context;
    private readonly ILogger<AgentSummaryRepository> _logger;

    public AgentSummaryRepository(ManagingDbContext context, ILogger<AgentSummaryRepository> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task<AgentSummary?> GetByUserIdAsync(int userId)
    {
        var entity = await _context.AgentSummaries
            .Include(a => a.User)
            .FirstOrDefaultAsync(a => a.UserId == userId);

        return entity != null ? MapToDomain(entity) : null;
    }

    public async Task<AgentSummary?> GetByAgentNameAsync(string agentName)
    {
        var entity = await _context.AgentSummaries
            .Include(a => a.User)
            .FirstOrDefaultAsync(a => a.AgentName == agentName);

        return entity != null ? MapToDomain(entity) : null;
    }

    public async Task<IEnumerable<AgentSummary>> GetAllAsync()
    {
        var entities = await _context.AgentSummaries
            .Include(a => a.User)
            .ToListAsync();

        return entities.Select(MapToDomain);
    }

    public async Task InsertAsync(AgentSummary agentSummary)
    {
        var entity = MapToEntity(agentSummary);
        entity.CreatedAt = DateTime.UtcNow;
        entity.UpdatedAt = DateTime.UtcNow;

        await _context.AgentSummaries.AddAsync(entity);
        await _context.SaveChangesAsync();

        _logger.LogInformation("AgentSummary inserted for user {UserId} with agent name {AgentName}",
            agentSummary.UserId, agentSummary.AgentName);
    }

    public async Task UpdateAsync(AgentSummary agentSummary)
    {
        var entity = await _context.AgentSummaries
            .FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);

        if (entity != null)
        {
            MapToEntity(agentSummary, entity);
            entity.UpdatedAt = DateTime.UtcNow;

            // No need to call Update() since the entity is already being tracked
            await _context.SaveChangesAsync();

            _logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
                agentSummary.UserId, agentSummary.AgentName);
        }
    }

    public async Task SaveOrUpdateAsync(AgentSummary agentSummary)
    {
        // Use the execution strategy to handle retries and transactions
        var strategy = _context.Database.CreateExecutionStrategy();
        await strategy.ExecuteAsync(async () =>
        {
            using var transaction = await _context.Database.BeginTransactionAsync();
            try
            {
                // Ensure the User entity exists and is saved
                if (agentSummary.User != null)
                {
                    var existingUser = await _context.Users
                        .AsNoTracking()
                        .FirstOrDefaultAsync(u => u.Id == agentSummary.UserId);

                    if (existingUser == null)
                    {
                        // User doesn't exist, save it first
                        var userEntity = PostgreSqlMappers.Map(agentSummary.User);
                        userEntity.Id = agentSummary.UserId; // Ensure the ID is set
                        await _context.Users.AddAsync(userEntity);
                        await _context.SaveChangesAsync();

                        _logger.LogInformation("User created for AgentSummary with ID {UserId}", agentSummary.UserId);
                    }
                }

                // Check if AgentSummary exists (using AsNoTracking to avoid tracking conflicts)
                var existing = await _context.AgentSummaries
                    .AsNoTracking()
                    .FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);

                if (existing == null)
                {
                    // Insert new AgentSummary
                    var entity = MapToEntity(agentSummary);
                    entity.CreatedAt = DateTime.UtcNow;
                    entity.UpdatedAt = DateTime.UtcNow;

                    await _context.AgentSummaries.AddAsync(entity);
                    await _context.SaveChangesAsync();

                    _logger.LogInformation("AgentSummary inserted for user {UserId} with agent name {AgentName}",
                        agentSummary.UserId, agentSummary.AgentName);
                }
                else
                {
                    // Update existing record - check if already tracked first
                    var entityToUpdate = MapToEntity(agentSummary);
                    entityToUpdate.Id = existing.Id; // Preserve the existing ID
                    entityToUpdate.CreatedAt = existing.CreatedAt; // Preserve creation date
                    entityToUpdate.UpdatedAt = DateTime.UtcNow;

                    // Check if an entity with this key is already being tracked
                    var trackedEntity = _context.ChangeTracker.Entries<AgentSummaryEntity>()
                        .FirstOrDefault(e => e.Entity.Id == existing.Id);
                    
                    if (trackedEntity != null)
                    {
                        // Entity is already tracked, update its values
                        MapToEntity(agentSummary, trackedEntity.Entity);
                        trackedEntity.Entity.UpdatedAt = DateTime.UtcNow;
                        trackedEntity.State = EntityState.Modified;
                    }
                    else
                    {
                        // Entity is not tracked, use Update method which handles state properly
                        _context.AgentSummaries.Update(entityToUpdate);
                    }
                    
                    await _context.SaveChangesAsync();

                    _logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
                        agentSummary.UserId, agentSummary.AgentName);
                }

                await transaction.CommitAsync();
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Error saving/updating AgentSummary for user {UserId} with agent name {AgentName}",
                    agentSummary.UserId, agentSummary.AgentName);
                throw;
            }
        });
    }

    public async Task<(IEnumerable<AgentSummary> Results, int TotalCount)> GetPaginatedAsync(
        int page,
        int pageSize,
        SortableFields sortBy,
        string sortOrder,
        IEnumerable<string>? agentNames = null)
    {
        // Start with base query
        var query = _context.AgentSummaries.Include(a => a.User).AsQueryable();

        // Apply agent name filtering if specified
        if (agentNames != null && agentNames.Any())
        {
            query = query.Where(a => agentNames.Contains(a.AgentName));
        }

        // Get total count before applying pagination
        var totalCount = await query.CountAsync();

        // Apply sorting
        var isDescending = sortOrder.ToLowerInvariant() == "desc";
        query = sortBy switch
        {
            SortableFields.TotalPnL => isDescending
                ? query.OrderByDescending(a => a.TotalPnL)
                : query.OrderBy(a => a.TotalPnL),
            SortableFields.TotalROI => isDescending
                ? query.OrderByDescending(a => a.TotalROI)
                : query.OrderBy(a => a.TotalROI),
            SortableFields.Wins => isDescending
                ? query.OrderByDescending(a => a.Wins)
                : query.OrderBy(a => a.Wins),
            SortableFields.Losses => isDescending
                ? query.OrderByDescending(a => a.Losses)
                : query.OrderBy(a => a.Losses),
            SortableFields.AgentName => isDescending
                ? query.OrderByDescending(a => a.AgentName)
                : query.OrderBy(a => a.AgentName),
            SortableFields.CreatedAt => isDescending
                ? query.OrderByDescending(a => a.CreatedAt)
                : query.OrderBy(a => a.CreatedAt),
            SortableFields.UpdatedAt => isDescending
                ? query.OrderByDescending(a => a.UpdatedAt)
                : query.OrderBy(a => a.UpdatedAt),
            _ => isDescending
                ? query.OrderByDescending(a => a.TotalPnL) // Default to TotalPnL desc
                : query.OrderBy(a => a.TotalPnL)
        };

        // Apply pagination
        var results = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        // Map to domain objects
        var domainResults = results.Select(MapToDomain);

        return (domainResults, totalCount);
    }

    private static AgentSummaryEntity MapToEntity(AgentSummary domain)
    {
        return new AgentSummaryEntity
        {
            Id = domain.Id,
            UserId = domain.UserId,
            AgentName = domain.AgentName,
            TotalPnL = domain.TotalPnL,
            TotalROI = domain.TotalROI,
            Wins = domain.Wins,
            Losses = domain.Losses,
            Runtime = domain.Runtime,
            CreatedAt = domain.CreatedAt,
            UpdatedAt = domain.UpdatedAt,
            ActiveStrategiesCount = domain.ActiveStrategiesCount,
            TotalVolume = domain.TotalVolume,
            TotalBalance = domain.TotalBalance
        };
    }

    private static void MapToEntity(AgentSummary domain, AgentSummaryEntity entity)
    {
        entity.UserId = domain.UserId;
        entity.AgentName = domain.AgentName;
        entity.TotalPnL = domain.TotalPnL;
        entity.TotalROI = domain.TotalROI;
        entity.Wins = domain.Wins;
        entity.Losses = domain.Losses;
        entity.Runtime = domain.Runtime;
        entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
        entity.TotalVolume = domain.TotalVolume;
        entity.TotalBalance = domain.TotalBalance;
    }

    private static AgentSummary MapToDomain(AgentSummaryEntity entity)
    {
        return new AgentSummary
        {
            Id = entity.Id,
            UserId = entity.UserId,
            AgentName = entity.AgentName,
            TotalPnL = entity.TotalPnL,
            TotalROI = entity.TotalROI,
            Wins = entity.Wins,
            Losses = entity.Losses,
            Runtime = entity.Runtime,
            CreatedAt = entity.CreatedAt,
            UpdatedAt = entity.UpdatedAt,
            ActiveStrategiesCount = entity.ActiveStrategiesCount,
            TotalVolume = entity.TotalVolume,
            TotalBalance = entity.TotalBalance,
            User = PostgreSqlMappers.Map(entity.User)
        };
    }

    public async Task<IEnumerable<AgentSummary>> GetAllAgentWithRunningBots()
    {
        var agentSummaries = await _context.AgentSummaries
            .Include(a => a.User)
            .Where(a => _context.Bots.Any(b => b.UserId == a.UserId && b.Status == BotStatus.Running))
            .ToListAsync();

        return agentSummaries.Select(MapToDomain);
    }
}