using System.Text.Json;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
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.Tests.Jobs;

public sealed class ProcessCVCheckJobTests : IDisposable
{
    private readonly ApplicationDbContext _dbContext;
    private readonly Mock<IFileStorageService> _fileStorageServiceMock;
    private readonly Mock<ICVParserService> _cvParserServiceMock;
    private readonly Mock<ICompanyVerifierService> _companyVerifierServiceMock;
    private readonly Mock<IEducationVerifierService> _educationVerifierServiceMock;
    private readonly Mock<ITimelineAnalyserService> _timelineAnalyserServiceMock;
    private readonly Mock<IAuditService> _auditServiceMock;
    private readonly Mock<ILogger<ProcessCVCheckJob>> _loggerMock;
    private readonly ProcessCVCheckJob _sut;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public ProcessCVCheckJobTests()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        _dbContext = new ApplicationDbContext(options);
        _fileStorageServiceMock = new Mock<IFileStorageService>();
        _cvParserServiceMock = new Mock<ICVParserService>();
        _companyVerifierServiceMock = new Mock<ICompanyVerifierService>();
        _educationVerifierServiceMock = new Mock<IEducationVerifierService>();
        _timelineAnalyserServiceMock = new Mock<ITimelineAnalyserService>();
        _auditServiceMock = new Mock<IAuditService>();
        _loggerMock = new Mock<ILogger<ProcessCVCheckJob>>();

        _sut = new ProcessCVCheckJob(
            _dbContext,
            _fileStorageServiceMock.Object,
            _cvParserServiceMock.Object,
            _companyVerifierServiceMock.Object,
            _educationVerifierServiceMock.Object,
            _timelineAnalyserServiceMock.Object,
            _auditServiceMock.Object,
            _loggerMock.Object);
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }

    #region Status Updates

    [Fact]
    public async Task ExecuteAsync_UpdatesStatusToProcessingAtStart()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        SetupDefaultMocks();

        // Create a flag to capture status change before completion
        CheckStatus? capturedStatusDuringProcessing = null;
        _fileStorageServiceMock
            .Setup(x => x.DownloadAsync(It.IsAny<string>()))
            .ReturnsAsync(() =>
            {
                // Capture the status when download is called (after status update to Processing)
                var check = _dbContext.CVChecks.AsNoTracking().First(c => c.Id == cvCheck.Id);
                capturedStatusDuringProcessing = check.Status;
                return new MemoryStream();
            });

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        capturedStatusDuringProcessing.Should().Be(CheckStatus.Processing);
    }

    [Fact]
    public async Task ExecuteAsync_UpdatesStatusToCompletedWhenDone()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        SetupDefaultMocks();

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.Status.Should().Be(CheckStatus.Completed);
        updatedCheck.CompletedAt.Should().NotBeNull();
        updatedCheck.CompletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public async Task ExecuteAsync_UpdatesStatusToFailedOnError()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        _fileStorageServiceMock
            .Setup(x => x.DownloadAsync(It.IsAny<string>()))
            .ThrowsAsync(new InvalidOperationException("Storage error"));

        // Act
        var act = () => _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<InvalidOperationException>();

        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.Status.Should().Be(CheckStatus.Failed);
    }

    #endregion

    #region File Operations

    [Fact]
    public async Task ExecuteAsync_DownloadsFileFromBlobStorage()
    {
        // Arrange
        var expectedBlobUrl = "https://storage.blob.core.windows.net/cvs/test-cv.pdf";
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending, blobUrl: expectedBlobUrl);
        SetupDefaultMocks();

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _fileStorageServiceMock.Verify(
            x => x.DownloadAsync(expectedBlobUrl),
            Times.Once);
    }

    #endregion

    #region CV Parsing

    [Fact]
    public async Task ExecuteAsync_ParsesCVAndSavesExtractedData()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending, fileName: "resume.pdf");
        var expectedCVData = CreateTestCVData();

        SetupDefaultMocks(cvData: expectedCVData);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _cvParserServiceMock.Verify(
            x => x.ParseAsync(It.IsAny<Stream>(), "resume.pdf", It.IsAny<CancellationToken>()),
            Times.Once);

        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.ExtractedDataJson.Should().NotBeNullOrEmpty();

        var deserializedData = JsonSerializer.Deserialize<CVData>(updatedCheck.ExtractedDataJson!, JsonOptions);
        deserializedData.Should().NotBeNull();
        deserializedData!.FullName.Should().Be(expectedCVData.FullName);
    }

    #endregion

    #region Employment Verification

    [Fact]
    public async Task ExecuteAsync_VerifiesEachEmploymentEntry()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 3);
        SetupDefaultMocks(cvData: cvData);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _companyVerifierServiceMock.Verify(
            x => x.VerifyCompanyAsync(
                It.IsAny<string>(),
                It.IsAny<DateOnly?>(),
                It.IsAny<DateOnly?>(),
                It.IsAny<string?>()),
            Times.Exactly(3));
    }

    #endregion

    #region Timeline Analysis

    [Fact]
    public async Task ExecuteAsync_AnalysesTimeline()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData();
        SetupDefaultMocks(cvData: cvData);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _timelineAnalyserServiceMock.Verify(
            x => x.Analyse(It.Is<List<EmploymentEntry>>(list => list.Count == cvData.Employment.Count)),
            Times.Once);
    }

    #endregion

    #region Score Calculation - All Verified Companies

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithAllVerifiedCompanies()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
        {
            ClaimedCompany = e.CompanyName,
            IsVerified = true,
            MatchScore = 100,
            MatchedCompanyName = e.CompanyName
        }).ToList();

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: CreateEmptyTimelineResult());

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(100);
    }

    #endregion

    #region Score Calculation - Unverified Companies

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithUnverifiedCompanies()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var verificationResults = new List<CompanyVerificationResult>
        {
            new()
            {
                ClaimedCompany = cvData.Employment[0].CompanyName,
                IsVerified = true,
                MatchScore = 100,
                MatchedCompanyName = cvData.Employment[0].CompanyName
            },
            new()
            {
                ClaimedCompany = cvData.Employment[1].CompanyName,
                IsVerified = false,
                MatchScore = 0,
                VerificationNotes = "Company not found"
            }
        };

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: CreateEmptyTimelineResult());

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - 10 for unverified = 90
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(90);
    }

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleUnverifiedCompanies()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 3);

        var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
        {
            ClaimedCompany = e.CompanyName,
            IsVerified = false,
            MatchScore = 0,
            VerificationNotes = "Company not found"
        }).ToList();

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: CreateEmptyTimelineResult());

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - (3 * 10) = 70
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(70);
    }

    #endregion

    #region Score Calculation - Gaps

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithGaps()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 5,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2023, 1, 1),
                    EndDate = new DateOnly(2023, 6, 1),
                    Months = 5
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - (5 * 1) = 95 (5 month gap at 1 per month)
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(95);
    }

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithGaps_MaxPenaltyOf10()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 15,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2022, 1, 1),
                    EndDate = new DateOnly(2023, 4, 1),
                    Months = 15 // 15 months gap, but max penalty is 10
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - min(15, 10) = 90 (max gap penalty is 10)
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(90);
    }

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleGaps()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 8,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2022, 1, 1),
                    EndDate = new DateOnly(2022, 4, 1),
                    Months = 3
                },
                new TimelineGap
                {
                    StartDate = new DateOnly(2023, 1, 1),
                    EndDate = new DateOnly(2023, 6, 1),
                    Months = 5
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - min(3,10) - min(5,10) = 100 - 3 - 5 = 92
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(92);
    }

    #endregion

    #region Score Calculation - Overlaps

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithOverlaps_NoDeductionIfLessThan2Months()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 2,
            Gaps = [],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "Company A",
                    Company2 = "Company B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2023, 3, 1),
                    Months = 2 // Exactly 2 months - no penalty (transition period)
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Base 100 - (2 - 2) * 2 = 100 (2 month transition allowed)
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(100);
    }

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithOverlaps_NoDeduction()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 5,
            Gaps = [],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "Company A",
                    Company2 = "Company B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2023, 6, 1),
                    Months = 5
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Overlaps are now informational only, no penalty
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(100);
    }

    [Fact]
    public async Task ExecuteAsync_CalculatesCorrectScore_WithMultipleOverlaps_NoDeduction()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 3);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 10,
            Gaps = [],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "Company A",
                    Company2 = "Company B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2023, 6, 1),
                    Months = 5
                },
                new TimelineOverlap
                {
                    Company1 = "Company B",
                    Company2 = "Company C",
                    OverlapStart = new DateOnly(2024, 1, 1),
                    OverlapEnd = new DateOnly(2024, 6, 1),
                    Months = 5
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Overlaps are informational only, no penalty
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(100);
    }

    #endregion

    #region CVFlag Records

    [Fact]
    public async Task ExecuteAsync_CreatesCVFlagRecordsForUnverifiedCompanies()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var verificationResults = new List<CompanyVerificationResult>
        {
            new()
            {
                ClaimedCompany = "Fake Corp",
                IsVerified = false,
                MatchScore = 0,
                VerificationNotes = "Company not found in registry"
            }
        };

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: CreateEmptyTimelineResult());

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Filter for specific flag type (job now creates multiple informational flags)
        var unverifiedFlags = flags.Where(f => f.Title == "Unverified Company").ToList();
        unverifiedFlags.Should().HaveCount(1);
        unverifiedFlags[0].Category.Should().Be(FlagCategory.Employment);
        unverifiedFlags[0].Severity.Should().Be(FlagSeverity.Warning);
        unverifiedFlags[0].ScoreImpact.Should().Be(-10);
    }

    [Fact]
    public async Task ExecuteAsync_CreatesCVFlagRecordsForGaps()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 3,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2023, 1, 1),
                    EndDate = new DateOnly(2023, 4, 1),
                    Months = 3
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Filter for specific flag type (job now creates multiple informational flags)
        var gapFlags = flags.Where(f => f.Title == "Employment Gap").ToList();
        gapFlags.Should().HaveCount(1);
        gapFlags[0].Category.Should().Be(FlagCategory.Timeline);
        gapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Less than 6 months
        gapFlags[0].ScoreImpact.Should().Be(-3);
    }

    [Fact]
    public async Task ExecuteAsync_CreatesCVFlagRecordsForGaps_WarningSeverityFor6PlusMonths()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 8,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2023, 1, 1),
                    EndDate = new DateOnly(2023, 9, 1),
                    Months = 8
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Filter for specific flag type (job now creates multiple informational flags)
        var gapFlags = flags.Where(f => f.Title == "Employment Gap").ToList();
        gapFlags.Should().HaveCount(1);
        gapFlags[0].Severity.Should().Be(FlagSeverity.Warning); // 6+ months
    }

    [Fact]
    public async Task ExecuteAsync_CreatesCVFlagRecordsForOverlaps()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 4,
            Gaps = [],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "Company A",
                    Company2 = "Company B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2023, 5, 1),
                    Months = 4
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Overlaps are now informational only (legitimate for part-time, consulting, transitions)
        var overlapFlags = flags.Where(f => f.Title == "Concurrent Employment").ToList();
        overlapFlags.Should().HaveCount(1);
        overlapFlags[0].Category.Should().Be(FlagCategory.Timeline);
        overlapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Informational only
        overlapFlags[0].ScoreImpact.Should().Be(0); // No penalty
    }

    [Fact]
    public async Task ExecuteAsync_CreatesCVFlagRecordsForOverlaps_InfoSeverityEvenFor6PlusMonths()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 8,
            Gaps = [],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "Company A",
                    Company2 = "Company B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2023, 9, 1),
                    Months = 8
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Overlaps are informational regardless of duration (common for part-time, consulting)
        var overlapFlags = flags.Where(f => f.Title == "Concurrent Employment").ToList();
        overlapFlags.Should().HaveCount(1);
        overlapFlags[0].Severity.Should().Be(FlagSeverity.Info); // Always info now
    }

    [Fact]
    public async Task ExecuteAsync_CreatesAppropriateCVFlagRecords_MultipleIssues()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 2);

        var verificationResults = new List<CompanyVerificationResult>
        {
            new()
            {
                ClaimedCompany = "Verified Corp",
                IsVerified = true,
                MatchScore = 100
            },
            new()
            {
                ClaimedCompany = "Unverified Inc",
                IsVerified = false,
                MatchScore = 0,
                VerificationNotes = "Not found"
            }
        };

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 3,
            TotalOverlapMonths = 0,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2023, 1, 1),
                    EndDate = new DateOnly(2023, 4, 1),
                    Months = 3
                }
            ],
            Overlaps = []
        };

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var flags = await _dbContext.CVFlags.Where(f => f.CVCheckId == cvCheck.Id).ToListAsync();

        // Check for specific penalty flags (job also creates informational flags)
        flags.Should().Contain(f => f.Title == "Unverified Company");
        flags.Should().Contain(f => f.Title == "Employment Gap");

        // Verify the specific penalty flags have correct values
        var unverifiedFlag = flags.First(f => f.Title == "Unverified Company");
        unverifiedFlag.ScoreImpact.Should().Be(-10);

        var gapFlag = flags.First(f => f.Title == "Employment Gap");
        gapFlag.ScoreImpact.Should().Be(-3);
    }

    #endregion

    #region Non-Existent Check

    [Fact]
    public async Task ExecuteAsync_WithNonExistentCheckId_HandlesGracefully()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();

        // Act
        await _sut.ExecuteAsync(nonExistentId, CancellationToken.None);

        // Assert - should complete without exception
        _fileStorageServiceMock.Verify(
            x => x.DownloadAsync(It.IsAny<string>()),
            Times.Never);
        _cvParserServiceMock.Verify(
            x => x.ParseAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
            Times.Never);
    }

    #endregion

    #region Score Floor

    [Fact]
    public async Task ExecuteAsync_ScoreNeverGoesBelowZero()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 15); // 15 unverified = -150 points

        var verificationResults = cvData.Employment.Select(e => new CompanyVerificationResult
        {
            ClaimedCompany = e.CompanyName,
            IsVerified = false,
            MatchScore = 0,
            VerificationNotes = "Not found"
        }).ToList();

        var timelineResult = new TimelineAnalysisResult
        {
            TotalGapMonths = 50, // Large gaps
            TotalOverlapMonths = 20,
            Gaps =
            [
                new TimelineGap
                {
                    StartDate = new DateOnly(2020, 1, 1),
                    EndDate = new DateOnly(2024, 3, 1),
                    Months = 50
                }
            ],
            Overlaps =
            [
                new TimelineOverlap
                {
                    Company1 = "A",
                    Company2 = "B",
                    OverlapStart = new DateOnly(2023, 1, 1),
                    OverlapEnd = new DateOnly(2024, 9, 1),
                    Months = 20
                }
            ]
        };

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: timelineResult);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert - Score should be 0, not negative
        // Penalties: 15 * 10 = 150 (unverified) + min(50,10) = 10 (gap) + (20-2)*2 = 36 (overlap) = 196
        // 100 - 196 = -96, but should be clamped to 0
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.VeracityScore.Should().Be(0);
    }

    #endregion

    #region Veracity Report

    [Fact]
    public async Task ExecuteAsync_GeneratesVeracityReportJson()
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);
        var cvData = CreateTestCVData(employmentCount: 1);
        SetupDefaultMocks(cvData: cvData);

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);
        updatedCheck.ReportJson.Should().NotBeNullOrEmpty();

        var report = JsonSerializer.Deserialize<VeracityReport>(updatedCheck.ReportJson!, JsonOptions);
        report.Should().NotBeNull();
        report!.OverallScore.Should().Be(updatedCheck.VeracityScore);
        report.GeneratedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1));
    }

    [Theory]
    [InlineData(0, "Excellent", 100)]   // 0 unverified = 100 >= 90 = Excellent
    [InlineData(1, "Excellent", 90)]    // 1 unverified = 90 >= 90 = Excellent
    [InlineData(2, "Good", 80)]         // 2 unverified = 80 >= 75 = Good
    [InlineData(3, "Fair", 70)]         // 3 unverified = 70 >= 60 = Fair (70 < 75)
    [InlineData(4, "Fair", 60)]         // 4 unverified = 60 >= 60 = Fair
    [InlineData(5, "Poor", 50)]         // 5 unverified = 50 >= 40 = Poor
    [InlineData(6, "Poor", 40)]         // 6 unverified = 40 >= 40 = Poor
    [InlineData(7, "Very Poor", 30)]    // 7 unverified = 30 < 40 = Very Poor
    [InlineData(10, "Very Poor", 0)]    // 10 unverified = 0 < 40 = Very Poor
    public async Task ExecuteAsync_GeneratesCorrectScoreLabel(int unverifiedCount, string expectedLabel, int expectedScore)
    {
        // Arrange
        var cvCheck = await CreateCVCheckInDatabase(CheckStatus.Pending);

        // Use exact number of unverified companies to get predictable score
        var employmentCount = Math.Max(1, unverifiedCount);
        var cvData = CreateTestCVData(employmentCount: employmentCount);

        var verificationResults = cvData.Employment.Select((e, i) => new CompanyVerificationResult
        {
            ClaimedCompany = e.CompanyName,
            IsVerified = i >= unverifiedCount, // First N are unverified
            MatchScore = i >= unverifiedCount ? 100 : 0,
            VerificationNotes = i >= unverifiedCount ? null : "Not found"
        }).ToList();

        SetupDefaultMocks(
            cvData: cvData,
            verificationResults: verificationResults,
            timelineResult: CreateEmptyTimelineResult());

        // Act
        await _sut.ExecuteAsync(cvCheck.Id, CancellationToken.None);

        // Assert
        _dbContext.ChangeTracker.Clear();
        var updatedCheck = await _dbContext.CVChecks.FirstAsync(c => c.Id == cvCheck.Id);

        updatedCheck.VeracityScore.Should().Be(expectedScore);

        var report = JsonSerializer.Deserialize<VeracityReport>(updatedCheck.ReportJson!, JsonOptions);
        report!.ScoreLabel.Should().Be(expectedLabel);
    }

    #endregion

    #region Helper Methods

    private async Task<CVCheck> CreateCVCheckInDatabase(
        CheckStatus status = CheckStatus.Pending,
        string blobUrl = "https://storage.blob.core.windows.net/cvs/test.pdf",
        string fileName = "test.pdf")
    {
        var cvCheck = new CVCheck
        {
            Id = Guid.NewGuid(),
            UserId = Guid.NewGuid(),
            OriginalFileName = fileName,
            BlobUrl = blobUrl,
            Status = status
        };

        _dbContext.CVChecks.Add(cvCheck);
        await _dbContext.SaveChangesAsync();
        _dbContext.ChangeTracker.Clear();

        return cvCheck;
    }

    private void SetupDefaultMocks(
        CVData? cvData = null,
        List<CompanyVerificationResult>? verificationResults = null,
        List<EducationVerificationResult>? educationResults = null,
        TimelineAnalysisResult? timelineResult = null)
    {
        cvData ??= CreateTestCVData();
        timelineResult ??= CreateEmptyTimelineResult();

        _fileStorageServiceMock
            .Setup(x => x.DownloadAsync(It.IsAny<string>()))
            .ReturnsAsync(new MemoryStream());

        _cvParserServiceMock
            .Setup(x => x.ParseAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(cvData);

        if (verificationResults != null)
        {
            var queue = new Queue<CompanyVerificationResult>(verificationResults);
            _companyVerifierServiceMock
                .Setup(x => x.VerifyCompanyAsync(
                    It.IsAny<string>(),
                    It.IsAny<DateOnly?>(),
                    It.IsAny<DateOnly?>(),
                    It.IsAny<string?>()))
                .ReturnsAsync(() => queue.Count > 0 ? queue.Dequeue() : CreateDefaultVerificationResult());
        }
        else
        {
            _companyVerifierServiceMock
                .Setup(x => x.VerifyCompanyAsync(
                    It.IsAny<string>(),
                    It.IsAny<DateOnly?>(),
                    It.IsAny<DateOnly?>(),
                    It.IsAny<string?>()))
                .ReturnsAsync(CreateDefaultVerificationResult());
        }

        _educationVerifierServiceMock
            .Setup(x => x.VerifyAll(
                It.IsAny<List<EducationEntry>>(),
                It.IsAny<List<EmploymentEntry>?>()))
            .Returns(educationResults ?? []);

        _timelineAnalyserServiceMock
            .Setup(x => x.Analyse(It.IsAny<List<EmploymentEntry>>()))
            .Returns(timelineResult);
    }

    private static CVData CreateTestCVData(int employmentCount = 1)
    {
        var employment = Enumerable.Range(0, employmentCount)
            .Select(i => new EmploymentEntry
            {
                CompanyName = $"Company {i + 1}",
                JobTitle = $"Developer {i + 1}",
                StartDate = new DateOnly(2020 + i, 1, 1),
                EndDate = new DateOnly(2021 + i, 1, 1)
            })
            .ToList();

        return new CVData
        {
            FullName = "John Doe",
            Email = "john.doe@example.com",
            Employment = employment
        };
    }

    private static TimelineAnalysisResult CreateEmptyTimelineResult()
    {
        return new TimelineAnalysisResult
        {
            TotalGapMonths = 0,
            TotalOverlapMonths = 0,
            Gaps = [],
            Overlaps = []
        };
    }

    private static CompanyVerificationResult CreateDefaultVerificationResult()
    {
        return new CompanyVerificationResult
        {
            ClaimedCompany = "Test Company",
            IsVerified = true,
            MatchScore = 100,
            MatchedCompanyName = "Test Company Ltd"
        };
    }

    #endregion
}
