#nullable enable
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Common;
using Baya.Application.Models.Verification;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using Baya.Domain.Entities.Verification;
using Baya.Infrastructure.Persistence.Repositories.Common;
using Microsoft.EntityFrameworkCore;

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class VerificationRepository : BaseAsyncRepository<NurseVerification>, IVerificationRepository
{
    public VerificationRepository(ApplicationDbContext dbContext) : base(dbContext)
    {
    }

    private IQueryable<VerificationStepType> StepTypes => DbContext.Set<VerificationStepType>();
    private IQueryable<VerificationStep> Steps => DbContext.Set<VerificationStep>();
    private IQueryable<VerificationDocument> Documents => DbContext.Set<VerificationDocument>();
    private IQueryable<NurseCredential> Credentials => DbContext.Set<NurseCredential>();
    private IQueryable<NurseProfile> Profiles => DbContext.Set<NurseProfile>();

    // --- Step-type catalog ---

    public Task<VerificationStepType?> GetStepTypeByIdAsync(long id, CancellationToken cancellationToken)
        => StepTypes.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);

    public Task<bool> StepTypeCodeExistsAsync(string code, CancellationToken cancellationToken)
        => StepTypes.AsNoTracking().AnyAsync(t => t.Code == code, cancellationToken);

    public Task<bool> StepTypeInUseAsync(long stepTypeId, CancellationToken cancellationToken)
        => Steps.AsNoTracking().AnyAsync(s => s.StepTypeId == stepTypeId, cancellationToken);

    public async Task AddStepTypeAsync(VerificationStepType stepType, CancellationToken cancellationToken)
        => await DbContext.Set<VerificationStepType>().AddAsync(stepType, cancellationToken);

    public Task<IReadOnlyList<VerificationStepTypeDto>> ListStepTypesAsync(bool includeInactive, CancellationToken cancellationToken)
        => ListStepTypesInternalAsync(includeInactive, cancellationToken);

    private async Task<IReadOnlyList<VerificationStepTypeDto>> ListStepTypesInternalAsync(bool includeInactive, CancellationToken cancellationToken)
    {
        var query = StepTypes.AsNoTracking();
        if (!includeInactive)
            query = query.Where(t => t.IsActive);

        return await query
            .OrderBy(t => t.SortOrder).ThenBy(t => t.Id)
            .Select(t => new VerificationStepTypeDto(
                t.Id, t.Code, t.DisplayName, t.Description, t.IsRequired, t.IsAutomated, t.AutomationProvider, t.SortOrder, t.IsActive))
            .ToListAsync(cancellationToken);
    }

    public async Task<IReadOnlyList<VerificationStepType>> GetActiveRequiredStepTypesAsync(CancellationToken cancellationToken)
        => await StepTypes.AsNoTracking()
            .Where(t => t.IsActive && t.IsRequired)
            .OrderBy(t => t.SortOrder)
            .ToListAsync(cancellationToken);

    // --- Nurse verification aggregate (tracked, for mutation) ---

    public Task<NurseVerification?> GetTrackedByNurseIdAsync(long nurseId, CancellationToken cancellationToken)
        => Table
            .Include(v => v.Steps).ThenInclude(s => s.StepType)
            .FirstOrDefaultAsync(v => v.NurseId == nurseId, cancellationToken);

    public Task<NurseVerification?> GetTrackedByIdAsync(long nurseVerificationId, CancellationToken cancellationToken)
        => Table
            .Include(v => v.Steps)
            .FirstOrDefaultAsync(v => v.Id == nurseVerificationId, cancellationToken);

    public async Task AddVerificationAsync(NurseVerification verification, CancellationToken cancellationToken)
        => await base.AddAsync(verification);

    public Task<VerificationStep?> GetTrackedStepForNurseAsync(long stepId, long nurseId, CancellationToken cancellationToken)
        => Steps
            .Include(s => s.StepType)
            .Include(s => s.NurseVerification).ThenInclude(v => v.Steps)
            .FirstOrDefaultAsync(s => s.Id == stepId && s.NurseVerification.NurseId == nurseId, cancellationToken);

    public Task<VerificationStep?> GetTrackedStepWithVerificationAsync(long stepId, CancellationToken cancellationToken)
        => Steps
            .Include(s => s.StepType)
            .Include(s => s.NurseVerification).ThenInclude(v => v.Steps)
            .FirstOrDefaultAsync(s => s.Id == stepId, cancellationToken);

    // --- Documents / credentials ---

    public async Task AddDocumentAsync(VerificationDocument document, CancellationToken cancellationToken)
        => await DbContext.Set<VerificationDocument>().AddAsync(document, cancellationToken);

    public async Task AddCredentialAsync(NurseCredential credential, CancellationToken cancellationToken)
        => await DbContext.Set<NurseCredential>().AddAsync(credential, cancellationToken);

    public Task<NurseCredential> GetTrackedCredentialAsync(long nurseId, string credentialType, CancellationToken cancellationToken)
        => DbContext.Set<NurseCredential>()
            .FirstOrDefaultAsync(c => c.NurseId == nurseId && c.CredentialType == credentialType, cancellationToken);

    // --- Projected reads ---

    public async Task<VerificationStatusDto?> GetStatusForNurseAsync(long nurseId, CancellationToken cancellationToken)
    {
        var header = await Table.AsNoTracking()
            .Where(v => v.NurseId == nurseId)
            .Select(v => new { v.Id, v.Status })
            .FirstOrDefaultAsync(cancellationToken);

        if (header is null)
            return null;

        // Flat read + in-memory assembly: SQLite can't translate a nested collection projection.
        var steps = await Steps.AsNoTracking()
            .Where(s => s.NurseVerificationId == header.Id)
            .OrderBy(s => s.StepType.SortOrder).ThenBy(s => s.Id)
            .Select(s => new
            {
                s.Id,
                Code = s.StepType.Code,
                Display = s.StepType.DisplayName,
                s.Status,
                s.IsAutomated,
                IsRequired = s.StepType.IsRequired,
                s.ExpiresAt,
                s.FailureReason
            })
            .ToListAsync(cancellationToken);

        var isBookable = await Profiles.AsNoTracking()
            .Where(p => p.Id == nurseId)
            .Select(p => p.IsVerified)
            .FirstOrDefaultAsync(cancellationToken);

        var stepDtos = steps
            .Select(s => new VerificationStepDto(s.Id, s.Code, s.Display, s.Status.ToCode(), s.IsAutomated, s.IsRequired, s.ExpiresAt, s.FailureReason))
            .ToList();

        var blocking = steps
            .Where(s => s.Status != VerificationStepStatus.Passed)
            .Select(s => s.Code)
            .ToList();

        return new VerificationStatusDto(header.Status.ToCode(), isBookable, blocking, stepDtos);
    }

    public async Task<PagedResult<AdminPendingStepDto>> ListPendingStepsAsync(
        VerificationStepStatus? status, int page, int pageSize, Func<string, string> signUrl, CancellationToken cancellationToken)
    {
        var effective = status ?? VerificationStepStatus.InReview;

        var query = Steps.AsNoTracking().Where(s => s.Status == effective);

        var total = await query.CountAsync(cancellationToken);

        var rows = await query
            .OrderBy(s => s.NurseVerification.SubmittedAt).ThenBy(s => s.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(s => new
            {
                s.NurseVerificationId,
                NurseId = s.NurseVerification.NurseId,
                NurseName = (s.NurseVerification.Nurse.User.Name ?? "") + " " + (s.NurseVerification.Nurse.User.FamilyName ?? ""),
                StepId = s.Id,
                StepCode = s.StepType.Code,
                StepDisplay = s.StepType.DisplayName,
                s.Status,
                SubmittedAt = s.NurseVerification.SubmittedAt
            })
            .ToListAsync(cancellationToken);

        var stepIds = rows.Select(r => r.StepId).ToList();
        var docs = await LoadDocumentsAsync(stepIds, signUrl, cancellationToken);

        var items = rows
            .Select(r => new AdminPendingStepDto(
                r.NurseVerificationId,
                r.NurseId,
                r.NurseName.Trim(),
                r.StepId,
                r.StepCode,
                r.StepDisplay,
                r.Status.ToCode(),
                r.SubmittedAt,
                docs.TryGetValue(r.StepId, out var stepDocs) ? stepDocs : []))
            .ToList();

        return new PagedResult<AdminPendingStepDto>(items, total, page, pageSize);
    }

    public async Task<AdminVerificationDetailDto?> GetDetailAsync(long nurseVerificationId, Func<string, string> signUrl, CancellationToken cancellationToken)
    {
        var header = await Table.AsNoTracking()
            .Where(v => v.Id == nurseVerificationId)
            .Select(v => new
            {
                v.Id,
                v.NurseId,
                v.Status,
                IdentityName = (v.Nurse.User.Name ?? "") + " " + (v.Nurse.User.FamilyName ?? "")
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (header is null)
            return null;

        var steps = await Steps.AsNoTracking()
            .Where(s => s.NurseVerificationId == header.Id)
            .OrderBy(s => s.StepType.SortOrder).ThenBy(s => s.Id)
            .Select(s => new
            {
                s.Id,
                Code = s.StepType.Code,
                Display = s.StepType.DisplayName,
                s.Status,
                s.IsAutomated,
                IsRequired = s.StepType.IsRequired,
                s.ExpiresAt,
                s.FailureReason
            })
            .ToListAsync(cancellationToken);

        var stepIds = steps.Select(s => s.Id).ToList();
        var docs = await LoadDocumentsAsync(stepIds, signUrl, cancellationToken);

        var credentials = await Credentials.AsNoTracking()
            .Where(c => c.NurseId == header.NurseId)
            .OrderByDescending(c => c.Id)
            .Select(c => new NurseCredentialDto(
                c.Id, c.CredentialType, c.HolderNameSnapshot, c.IssuingAuthority, c.IssuedAt, c.ExpiresAt, c.VerificationMethod))
            .ToListAsync(cancellationToken);

        var stepDtos = steps
            .Select(s => new AdminStepDetailDto(
                s.Id, s.Code, s.Display, s.Status.ToCode(), s.IsAutomated, s.ExpiresAt, s.FailureReason,
                docs.TryGetValue(s.Id, out var stepDocs) ? stepDocs : []))
            .ToList();

        return new AdminVerificationDetailDto(header.Id, header.NurseId, header.IdentityName.Trim(), header.Status.ToCode(), stepDtos, credentials);
    }

    public async Task<TrustBadgeDto?> GetTrustBadgeAsync(long nurseId, DateOnly today, CancellationToken cancellationToken)
    {
        var profile = await Profiles.AsNoTracking()
            .Where(p => p.Id == nurseId)
            .Select(p => new { p.IsVerified })
            .FirstOrDefaultAsync(cancellationToken);

        if (profile is null)
            return null;

        var approvedAt = await Table.AsNoTracking()
            .Where(v => v.NurseId == nurseId)
            .Select(v => v.ApprovedAt)
            .FirstOrDefaultAsync(cancellationToken);

        var credentialTypes = await Credentials.AsNoTracking()
            .Where(c => c.NurseId == nurseId && (c.ExpiresAt == null || c.ExpiresAt >= today))
            .Select(c => c.CredentialType)
            .Distinct()
            .ToListAsync(cancellationToken);

        credentialTypes.Sort(StringComparer.Ordinal);

        return new TrustBadgeDto(nurseId, profile.IsVerified, approvedAt, credentialTypes);
    }

    public Task<string?> GetNurseIdentityNameAsync(long nurseId, CancellationToken cancellationToken)
        => Profiles.AsNoTracking()
            .Where(p => p.Id == nurseId)
            .Select(p => (p.User.Name ?? "") + " " + (p.User.FamilyName ?? ""))
            .FirstOrDefaultAsync(cancellationToken);

    public Task<User?> GetTrackedUserAsync(int userId, CancellationToken cancellationToken)
        => DbContext.Set<User>().FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);

    public async Task<IReadOnlyList<ExpiringStepRow>> GetExpiredPassedStepsAsync(
        DateTimeOffset asOf, int page, int pageSize, CancellationToken cancellationToken)
        => await Steps.AsNoTracking()
            .Where(s => s.Status == VerificationStepStatus.Passed && s.ExpiresAt != null && s.ExpiresAt < asOf)
            .OrderBy(s => s.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(s => new ExpiringStepRow(s.Id, s.NurseVerificationId, s.NurseVerification.NurseId))
            .ToListAsync(cancellationToken);

    private async Task<Dictionary<long, IReadOnlyList<VerificationDocumentDto>>> LoadDocumentsAsync(
        IReadOnlyList<long> stepIds, Func<string, string> signUrl, CancellationToken cancellationToken)
    {
        if (stepIds.Count == 0)
            return new Dictionary<long, IReadOnlyList<VerificationDocumentDto>>();

        var rows = await Documents.AsNoTracking()
            .Where(d => stepIds.Contains(d.StepId))
            .OrderBy(d => d.Id)
            .Select(d => new { d.Id, d.StepId, d.ContentType, d.FileSizeBytes, d.OriginalFileName, d.ObjectStorageKey })
            .ToListAsync(cancellationToken);

        return rows
            .GroupBy(d => d.StepId)
            .ToDictionary(
                g => g.Key,
                g => (IReadOnlyList<VerificationDocumentDto>)g
                    .Select(d => new VerificationDocumentDto(d.Id, d.ContentType, d.FileSizeBytes, d.OriginalFileName, signUrl(d.ObjectStorageKey)))
                    .ToList());
    }
}
