using System.Text.Json; using Hangfire; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using RealCV.Application.DTOs; using RealCV.Application.Helpers; using RealCV.Application.Interfaces; using RealCV.Application.Models; using RealCV.Domain.Entities; using RealCV.Domain.Enums; using RealCV.Infrastructure.Data; using RealCV.Infrastructure.Jobs; namespace RealCV.Infrastructure.Services; public sealed class CVCheckService : ICVCheckService { private readonly ApplicationDbContext _dbContext; private readonly IFileStorageService _fileStorageService; private readonly IBackgroundJobClient _backgroundJobClient; private readonly IAuditService _auditService; private readonly ILogger _logger; public CVCheckService( ApplicationDbContext dbContext, IFileStorageService fileStorageService, IBackgroundJobClient backgroundJobClient, IAuditService auditService, ILogger logger) { _dbContext = dbContext; _fileStorageService = fileStorageService; _backgroundJobClient = backgroundJobClient; _auditService = auditService; _logger = logger; } public async Task CreateCheckAsync(Guid userId, Stream file, string fileName) { ArgumentNullException.ThrowIfNull(file); ArgumentException.ThrowIfNullOrWhiteSpace(fileName); _logger.LogDebug("Creating CV check for user {UserId}, file: {FileName}", userId, fileName); // Upload file to blob storage var blobUrl = await _fileStorageService.UploadAsync(file, fileName); _logger.LogDebug("File uploaded to: {BlobUrl}", blobUrl); // Create CV check record var cvCheck = new CVCheck { Id = Guid.NewGuid(), UserId = userId, OriginalFileName = fileName, BlobUrl = blobUrl, Status = CheckStatus.Pending }; _dbContext.CVChecks.Add(cvCheck); await _dbContext.SaveChangesAsync(); _logger.LogDebug("CV check record created with ID: {CheckId}", cvCheck.Id); // Queue background job for processing _backgroundJobClient.Enqueue(job => job.ExecuteAsync(cvCheck.Id, CancellationToken.None)); _logger.LogInformation( "CV check {CheckId} created for user {UserId}, processing queued", cvCheck.Id, userId); await _auditService.LogAsync(userId, AuditActions.CVUploaded, "CVCheck", cvCheck.Id, $"File: {fileName}"); return cvCheck.Id; } public async Task GetCheckAsync(Guid id) { _logger.LogDebug("Retrieving CV check: {CheckId}", id); var cvCheck = await _dbContext.CVChecks .AsNoTracking() .FirstOrDefaultAsync(c => c.Id == id); if (cvCheck is null) { _logger.LogDebug("CV check not found: {CheckId}", id); return null; } return MapToDto(cvCheck); } public async Task> GetUserChecksAsync(Guid userId) { _logger.LogDebug("Retrieving CV checks for user: {UserId}", userId); var checks = await _dbContext.CVChecks .AsNoTracking() .Where(c => c.UserId == userId) .OrderByDescending(c => c.CreatedAt) .ToListAsync(); _logger.LogDebug("Found {Count} CV checks for user {UserId}", checks.Count, userId); return checks.Select(MapToDto).ToList(); } public async Task GetCheckForUserAsync(Guid id, Guid userId) { _logger.LogDebug("Retrieving CV check {CheckId} for user {UserId}", id, userId); var cvCheck = await _dbContext.CVChecks .AsNoTracking() .FirstOrDefaultAsync(c => c.Id == id && c.UserId == userId); if (cvCheck is null) { _logger.LogDebug("CV check not found: {CheckId} for user {UserId}", id, userId); return null; } return MapToDto(cvCheck); } public async Task GetReportAsync(Guid checkId, Guid userId) { _logger.LogDebug("Retrieving report for CV check {CheckId}, user {UserId}", checkId, userId); var cvCheck = await _dbContext.CVChecks .AsNoTracking() .FirstOrDefaultAsync(c => c.Id == checkId && c.UserId == userId); if (cvCheck is null) { _logger.LogWarning("CV check not found: {CheckId} for user {UserId}", checkId, userId); return null; } if (cvCheck.Status != CheckStatus.Completed || string.IsNullOrEmpty(cvCheck.ReportJson)) { _logger.LogDebug("CV check {CheckId} not completed or has no report", checkId); return null; } try { var report = JsonSerializer.Deserialize(cvCheck.ReportJson, JsonDefaults.CamelCase); return report; } catch (JsonException ex) { _logger.LogError(ex, "Failed to deserialize report JSON for check {CheckId}", checkId); return null; } } public async Task DeleteCheckAsync(Guid checkId, Guid userId) { _logger.LogDebug("Deleting CV check {CheckId} for user {UserId}", checkId, userId); var cvCheck = await _dbContext.CVChecks .Include(c => c.Flags) .FirstOrDefaultAsync(c => c.Id == checkId && c.UserId == userId); if (cvCheck is null) { _logger.LogWarning("CV check {CheckId} not found for user {UserId}", checkId, userId); return false; } var fileName = cvCheck.OriginalFileName; _dbContext.CVFlags.RemoveRange(cvCheck.Flags); _dbContext.CVChecks.Remove(cvCheck); await _dbContext.SaveChangesAsync(); _logger.LogInformation("Deleted CV check {CheckId} for user {UserId}", checkId, userId); await _auditService.LogAsync(userId, AuditActions.CVDeleted, "CVCheck", checkId, $"File: {fileName}"); return true; } private static CVCheckDto MapToDto(CVCheck cvCheck) { return new CVCheckDto { Id = cvCheck.Id, OriginalFileName = cvCheck.OriginalFileName, Status = cvCheck.Status.ToString(), VeracityScore = cvCheck.VeracityScore, ProcessingStage = cvCheck.ProcessingStage, CreatedAt = cvCheck.CreatedAt, CompletedAt = cvCheck.CompletedAt }; } }