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.Domain.Exceptions;
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 ISubscriptionService _subscriptionService;
    private readonly ILogger<CVCheckService> _logger;

    public CVCheckService(
        ApplicationDbContext dbContext,
        IFileStorageService fileStorageService,
        IBackgroundJobClient backgroundJobClient,
        IAuditService auditService,
        ISubscriptionService subscriptionService,
        ILogger<CVCheckService> logger)
    {
        _dbContext = dbContext;
        _fileStorageService = fileStorageService;
        _backgroundJobClient = backgroundJobClient;
        _auditService = auditService;
        _subscriptionService = subscriptionService;
        _logger = logger;
    }

    public async Task<Guid> 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);

        // Check quota before proceeding
        if (!await _subscriptionService.CanPerformCheckAsync(userId))
        {
            _logger.LogWarning("User {UserId} quota exceeded - CV check denied", userId);
            throw new QuotaExceededException();
        }

        // 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<ProcessCVCheckJob>(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}");

        // Increment usage after successful creation
        await _subscriptionService.IncrementUsageAsync(userId);

        return cvCheck.Id;
    }

    public async Task<CVCheckDto?> 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<List<CVCheckDto>> 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<CVCheckDto?> 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<VeracityReport?> 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<VeracityReport>(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<bool> 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;

        // GDPR: Delete the uploaded file if it still exists
        if (!string.IsNullOrWhiteSpace(cvCheck.BlobUrl))
        {
            try
            {
                await _fileStorageService.DeleteAsync(cvCheck.BlobUrl);
                _logger.LogDebug("Deleted file for CV check {CheckId}", checkId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", checkId);
                // Continue with deletion even if file deletion fails
            }
        }

        _dbContext.CVFlags.RemoveRange(cvCheck.Flags);
        _dbContext.CVChecks.Remove(cvCheck);
        await _dbContext.SaveChangesAsync();

        _logger.LogInformation("GDPR: Deleted CV check {CheckId} and associated data for user {UserId}", checkId, userId);

        await _auditService.LogAsync(userId, AuditActions.CVDeleted, "CVCheck", checkId, $"File: {fileName}");

        return true;
    }

    public async Task<int> DeleteAllUserDataAsync(Guid userId)
    {
        _logger.LogInformation("GDPR: Deleting all CV data for user {UserId}", userId);

        var userChecks = await _dbContext.CVChecks
            .Include(c => c.Flags)
            .Where(c => c.UserId == userId)
            .ToListAsync();

        if (userChecks.Count == 0)
        {
            _logger.LogDebug("No CV checks found for user {UserId}", userId);
            return 0;
        }

        var deletedCount = 0;

        foreach (var check in userChecks)
        {
            // Delete the file if it exists
            if (!string.IsNullOrWhiteSpace(check.BlobUrl))
            {
                try
                {
                    await _fileStorageService.DeleteAsync(check.BlobUrl);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", check.Id);
                }
            }

            _dbContext.CVFlags.RemoveRange(check.Flags);
            _dbContext.CVChecks.Remove(check);
            deletedCount++;
        }

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation("GDPR: Deleted {Count} CV checks for user {UserId}", deletedCount, userId);

        await _auditService.LogAsync(userId, AuditActions.CVDeleted, null, null, $"Deleted all data: {deletedCount} checks");

        return deletedCount;
    }

    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
        };
    }
}
