using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;

public class QsofaService : IQsofaService
{
    private readonly AppDbContext _db;
    private readonly IConnectionMultiplexer _redis;

    public QsofaService(AppDbContext db, IConnectionMultiplexer redis)
    {
        _db = db;
        _redis = redis;
    }

    public async Task<QsofaCurrentResponse> GetCurrentAsync(Guid encounterId)
    {
        await EnsureEncounterExistsAsync(encounterId);

        var values = await ReadCriterionValuesAsync(encounterId);
        return BuildResponse(values);
    }

    public async Task<int> GetActiveCriteriaCountAsync(Guid encounterId)
    {
        var values = await ReadCriterionValuesAsync(encounterId);
        return QsofaCalculator.CountActiveCriteria(values);
    }

    public async Task<CursorPage<QsofaEvaluation>> GetHistoryAsync(
        Guid encounterId, int limit, string? cursorToken)
    {
        limit = Math.Clamp(limit, 1, 100);
        var cursor = QsofaEvaluationCursor.Decode(cursorToken);

        var query = _db.QsofaEvaluations
            .AsNoTracking()
            .Where(e => e.EncounterId == encounterId);

        if (cursor is not null)
        {
            query = query.Where(e =>
                e.EvaluatedAt < cursor.EvaluatedAt ||
                (e.EvaluatedAt == cursor.EvaluatedAt && e.Id.CompareTo(cursor.Id) < 0));
        }

        var items = await query
            .OrderByDescending(e => e.EvaluatedAt)
            .ThenByDescending(e => e.Id)
            .Take(limit + 1)
            .ToListAsync();

        var hasMore = items.Count > limit;
        if (hasMore) items.RemoveAt(limit);

        var nextCursor = hasMore
            ? new QsofaEvaluationCursor(items[^1].EvaluatedAt, items[^1].Id).Encode()
            : null;

        return new CursorPage<QsofaEvaluation>(items, nextCursor, hasMore);
    }

    private async Task EnsureEncounterExistsAsync(Guid encounterId)
    {
        var exists = await _db.Encounters.AnyAsync(e => e.Id == encounterId);
        if (!exists)
            throw new NotFoundException("Encounter not found.", "ENCOUNTER_NOT_FOUND");
    }

    private async Task<RedisValue[]> ReadCriterionValuesAsync(Guid encounterId)
    {
        var cache = _redis.GetDatabase();
        return await cache.StringGetAsync(QsofaCalculator.AllCriterionKeys(encounterId));
    }

    private static QsofaCurrentResponse BuildResponse(RedisValue[] values)
    {
        return new QsofaCurrentResponse(
            QsofaCalculator.CountActiveCriteria(values),
            QsofaCalculator.ParseCriteriaState(values));
    }
}
