using Microsoft.EntityFrameworkCore;

public class CoverSheetService : ICoverSheetService
{
    private readonly AppDbContext _db;
    private readonly ILogger<CoverSheetService> _logger;

    public CoverSheetService(AppDbContext db, ILogger<CoverSheetService> logger)
    {
        _db = db;
        _logger = logger;
    }

    public async Task<List<CoverSheet>> GenerateAsync(
        GenerateCoverSheetsRequest request, Guid actorUserId)
    {
        var count = Math.Clamp(request.Count, 1, 100);
        var batchType = BatchTypeExtensions.FromDbString(request.BatchType.ToUpperInvariant());
        var track = string.IsNullOrEmpty(request.Track)
            ? BatchTrack.Backfill
            : BatchTrackExtensions.FromDbString(request.Track.ToUpperInvariant());

        if (request.PatientId.HasValue)
        {
            var patientExists = await _db.Patients.AnyAsync(p => p.Id == request.PatientId.Value);
            if (!patientExists)
                throw new NotFoundException("Patient not found.", "PATIENT_NOT_FOUND");
        }

        if (request.AssignToUserId.HasValue)
        {
            var user = await _db.Users.FindAsync(request.AssignToUserId.Value);
            if (user is null || !user.IsActive)
                throw new NotFoundException("User not found or inactive.", "USER_NOT_FOUND");
        }

        var sheets = new List<CoverSheet>(count);
        for (var i = 0; i < count; i++)
        {
            var code = $"VCR-CS-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
            sheets.Add(new CoverSheet
            {
                Id = Guid.NewGuid(),
                Code = code,
                BatchType = batchType,
                Track = track,
                PatientId = request.PatientId,
                AssignToUserId = request.AssignToUserId,
                GeneratedByUserId = actorUserId,
                IsUsed = false,
                CreatedAt = DateTimeOffset.UtcNow,
            });
        }

        _db.CoverSheets.AddRange(sheets);
        await _db.SaveChangesAsync();

        _logger.LogInformation(
            "Generated {Count} cover sheets for batch type {BatchType}, track {Track}",
            count, batchType.ToDbString(), track.ToDbString());

        return sheets;
    }

    public async Task<CoverSheet?> LookupByCodeAsync(string code)
    {
        return await _db.CoverSheets
            .Include(c => c.Patient)
            .Include(c => c.AssignToUser)
            .FirstOrDefaultAsync(c => c.Code == code.ToUpperInvariant().Trim());
    }

    public async Task<CoverSheet?> LookupByIdAsync(Guid id)
    {
        return await _db.CoverSheets
            .Include(c => c.Patient)
            .Include(c => c.AssignToUser)
            .FirstOrDefaultAsync(c => c.Id == id);
    }

    public async Task<List<CoverSheet>> GetByIdsAsync(IReadOnlyList<Guid> ids)
    {
        if (ids.Count == 0)
            return new List<CoverSheet>();

        var idSet = ids.Distinct().ToList();
        var sheets = await _db.CoverSheets
            .AsNoTracking()
            .Include(c => c.Patient)
            .Include(c => c.AssignToUser)
            .Where(c => idSet.Contains(c.Id))
            .ToListAsync();

        var byId = sheets.ToDictionary(c => c.Id);
        return idSet
            .Where(byId.ContainsKey)
            .Select(id => byId[id])
            .ToList();
    }

    public async Task RedeemAsync(Guid coverSheetId, Guid batchId)
    {
        var sheet = await _db.CoverSheets.FindAsync(coverSheetId);
        if (sheet is null)
            throw new NotFoundException("Cover sheet not found.", "COVER_SHEET_NOT_FOUND");

        if (sheet.IsUsed)
            throw new ConflictException(
                $"Cover sheet {sheet.Code} has already been used for batch {sheet.BatchId}.",
                "COVER_SHEET_ALREADY_USED");

        sheet.IsUsed = true;
        sheet.BatchId = batchId;
        sheet.UsedAt = DateTimeOffset.UtcNow;
        await _db.SaveChangesAsync();
    }

    public async Task<List<CoverSheet>> ListAsync(
        bool? isUsed, Guid? patientId, int page, int pageSize)
    {
        var query = _db.CoverSheets
            .AsNoTracking()
            .Include(c => c.Patient)
            .Include(c => c.AssignToUser)
            .AsQueryable();

        if (isUsed.HasValue)
            query = query.Where(c => c.IsUsed == isUsed.Value);
        if (patientId.HasValue)
            query = query.Where(c => c.PatientId == patientId.Value);

        return await query
            .OrderByDescending(c => c.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
    }
}