using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using RealCV.Application.Models;
using RealCV.Infrastructure.Services;

namespace RealCV.Tests.Services;

public class TimelineAnalyserServiceTests
{
    private readonly TimelineAnalyserService _sut;
    private readonly Mock<ILogger<TimelineAnalyserService>> _loggerMock;

    public TimelineAnalyserServiceTests()
    {
        _loggerMock = new Mock<ILogger<TimelineAnalyserService>>();
        _sut = new TimelineAnalyserService(_loggerMock.Object);
    }

    #region Empty and Single Entry Tests

    [Fact]
    public void Analyse_WithEmptyEmploymentList_ReturnsEmptyResults()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>();

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.TotalGapMonths.Should().Be(0);
        result.TotalOverlapMonths.Should().Be(0);
        result.Gaps.Should().BeEmpty();
        result.Overlaps.Should().BeEmpty();
    }

    [Fact]
    public void Analyse_WithSingleEmploymentEntry_ReturnsNoGapsOrOverlaps()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.TotalGapMonths.Should().Be(0);
        result.TotalOverlapMonths.Should().Be(0);
        result.Gaps.Should().BeEmpty();
        result.Overlaps.Should().BeEmpty();
    }

    #endregion

    #region Gap Detection Tests

    [Fact]
    public void Analyse_WithTwoConsecutiveJobs_ReturnsNoGaps()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
            CreateEmployment("Company B", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.TotalGapMonths.Should().Be(0);
        result.Gaps.Should().BeEmpty();
    }

    [Fact]
    public void Analyse_WithThreeMonthGap_DetectsGapCorrectly()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", new DateOnly(2020, 10, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].StartDate.Should().Be(new DateOnly(2020, 6, 30));
        result.Gaps[0].EndDate.Should().Be(new DateOnly(2020, 10, 1));
        result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(3);
        result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(3);
    }

    [Fact]
    public void Analyse_WithLargeGap_DetectsGapWithCorrectMonths()
    {
        // Arrange - 6 month gap
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 15)),
            CreateEmployment("Company B", new DateOnly(2021, 1, 15), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(1);
        result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(6);
    }

    [Fact]
    public void Analyse_WithSmallGapUnderThreeMonths_DoesNotFlagIt()
    {
        // Arrange - 2 month gap (should not be flagged)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", new DateOnly(2020, 8, 15), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().BeEmpty();
        result.TotalGapMonths.Should().Be(0);
    }

    [Fact]
    public void Analyse_WithOneMonthGap_DoesNotFlagIt()
    {
        // Arrange - 1 month gap
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", new DateOnly(2020, 8, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().BeEmpty();
        result.TotalGapMonths.Should().Be(0);
    }

    #endregion

    #region Overlap Detection Tests

    [Fact]
    public void Analyse_WithOverlappingJobsMoreThanTwoMonths_DetectsOverlap()
    {
        // Arrange - 4 month overlap
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 5, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().HaveCount(1);
        result.Overlaps[0].Company1.Should().Be("Company A");
        result.Overlaps[0].Company2.Should().Be("Company B");
        result.Overlaps[0].Months.Should().BeGreaterThan(2);
        result.TotalOverlapMonths.Should().BeGreaterThan(2);
    }

    [Fact]
    public void Analyse_WithOneMonthTransitionOverlap_DoesNotFlagIt()
    {
        // Arrange - 1 month overlap (transition period, should be allowed)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 15)),
            CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().BeEmpty();
        result.TotalOverlapMonths.Should().Be(0);
    }

    [Fact]
    public void Analyse_WithTwoMonthTransitionOverlap_DoesNotFlagIt()
    {
        // Arrange - 2 month overlap (transition period, should be allowed)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 7, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().BeEmpty();
        result.TotalOverlapMonths.Should().Be(0);
    }

    [Fact]
    public void Analyse_WithThreeMonthOverlap_DetectsOverlap()
    {
        // Arrange - 3 month overlap (exceeds 2 month allowed period)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().HaveCount(1);
        result.Overlaps[0].Months.Should().BeGreaterThan(2);
    }

    #endregion

    #region Mixed Scenarios Tests

    [Fact]
    public void Analyse_WithMultipleJobsMixedGapsAndOverlaps_DetectsAll()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            // Job 1: Jan 2019 - Dec 2019
            CreateEmployment("Company A", new DateOnly(2019, 1, 1), new DateOnly(2019, 12, 31)),
            // Job 2: June 2020 - Dec 2020 (6 month gap after Job 1 - should be detected)
            CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2020, 12, 31)),
            // Job 3: Sep 2020 - June 2021 (4 month overlap with Job 2 - should be detected)
            CreateEmployment("Company C", new DateOnly(2020, 9, 1), new DateOnly(2021, 6, 30))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].StartDate.Should().Be(new DateOnly(2019, 12, 31));
        result.Gaps[0].EndDate.Should().Be(new DateOnly(2020, 6, 1));

        result.Overlaps.Should().HaveCount(1);
        result.Overlaps[0].Company1.Should().Be("Company B");
        result.Overlaps[0].Company2.Should().Be("Company C");
    }

    [Fact]
    public void Analyse_WithMultipleGaps_SumsTotalGapMonths()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2019, 1, 1), new DateOnly(2019, 6, 30)),
            // 4 month gap
            CreateEmployment("Company B", new DateOnly(2019, 11, 1), new DateOnly(2020, 4, 30)),
            // 5 month gap
            CreateEmployment("Company C", new DateOnly(2020, 10, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(2);
        result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(8);
    }

    [Fact]
    public void Analyse_WithMultipleOverlaps_SumsTotalOverlapMonths()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 9, 30)),
            CreateEmployment("Company B", new DateOnly(2020, 5, 1), new DateOnly(2021, 3, 31)),
            CreateEmployment("Company C", new DateOnly(2020, 12, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().HaveCountGreaterThan(0);
        result.TotalOverlapMonths.Should().BeGreaterThan(0);
    }

    #endregion

    #region Null Date Handling Tests

    [Fact]
    public void Analyse_WithAllNullStartDates_ReturnsEmptyResults()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", null, new DateOnly(2020, 12, 31)),
            CreateEmployment("Company B", null, new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.TotalGapMonths.Should().Be(0);
        result.TotalOverlapMonths.Should().Be(0);
        result.Gaps.Should().BeEmpty();
        result.Overlaps.Should().BeEmpty();
    }

    [Fact]
    public void Analyse_WithSomeNullStartDates_FiltersThemOut()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", null, new DateOnly(2020, 12, 31)),
            CreateEmployment("Company C", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        // Company B is filtered out, gap between A and C is 6 months
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(6);
    }

    [Fact]
    public void Analyse_WithMixedNullDates_HandlesGracefully()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", null, null),
            CreateEmployment("Company B", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
            CreateEmployment("Company C", null, new DateOnly(2021, 6, 30))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert - only Company B has valid start date, so no gaps/overlaps possible
        result.Gaps.Should().BeEmpty();
        result.Overlaps.Should().BeEmpty();
    }

    #endregion

    #region Current Job (No End Date) Tests

    [Fact]
    public void Analyse_WithCurrentJobNoEndDate_UsesTodayAsEndDate()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateCurrentEmployment("Company B", new DateOnly(2020, 7, 1))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().BeEmpty();
        result.TotalGapMonths.Should().Be(0);
    }

    [Fact]
    public void Analyse_WithCurrentJobOverlappingPastJob_DetectsOverlap()
    {
        // Arrange - Current job started while still at previous job
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31)),
            CreateCurrentEmployment("Company B", new DateOnly(2024, 6, 1))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().HaveCount(1);
        result.Overlaps[0].Company1.Should().Be("Company A");
        result.Overlaps[0].Company2.Should().Be("Company B");
        result.Overlaps[0].Months.Should().BeGreaterThan(2);
    }

    [Fact]
    public void Analyse_WithTwoCurrentJobs_DetectsLargeOverlap()
    {
        // Arrange - Two concurrent current positions (unusual but valid scenario)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateCurrentEmployment("Company A", new DateOnly(2023, 1, 1)),
            CreateCurrentEmployment("Company B", new DateOnly(2023, 6, 1))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert - Should detect substantial overlap since both run to today
        result.Overlaps.Should().HaveCount(1);
        result.TotalOverlapMonths.Should().BeGreaterThan(2);
    }

    [Fact]
    public void Analyse_WithGapBeforeCurrentJob_DetectsGap()
    {
        // Arrange
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2023, 1, 1), new DateOnly(2023, 6, 30)),
            CreateCurrentEmployment("Company B", new DateOnly(2024, 1, 1))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(6);
    }

    #endregion

    #region Edge Cases and Ordering Tests

    [Fact]
    public void Analyse_WithUnsortedEmployment_SortsByStartDate()
    {
        // Arrange - Jobs provided out of order
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company C", new DateOnly(2022, 1, 1), new DateOnly(2022, 12, 31)),
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert - Should detect gap between A (ends June 2020) and B (starts Jan 2021)
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].StartDate.Should().Be(new DateOnly(2020, 6, 30));
        result.Gaps[0].EndDate.Should().Be(new DateOnly(2021, 1, 1));
    }

    [Fact]
    public void Analyse_WithSameStartDate_HandlesCorrectly()
    {
        // Arrange - Two jobs starting on same date
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert - Should detect overlap
        result.Overlaps.Should().HaveCount(1);
    }

    [Fact]
    public void Analyse_WithNullList_ThrowsArgumentNullException()
    {
        // Arrange
        List<EmploymentEntry>? employmentHistory = null;

        // Act
        var act = () => _sut.Analyse(employmentHistory!);

        // Assert
        act.Should().Throw<ArgumentNullException>();
    }

    [Fact]
    public void Analyse_WithExactlyThreeMonthGap_DetectsGap()
    {
        // Arrange - Exactly 3 month gap (boundary condition)
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 3, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 7, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().HaveCount(1);
        result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(3);
    }

    [Fact]
    public void Analyse_WithJobEndingSameDayNextStarts_NoGap()
    {
        // Arrange - Seamless transition
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
            CreateEmployment("Company B", new DateOnly(2020, 6, 30), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Gaps.Should().BeEmpty();
    }

    [Fact]
    public void Analyse_WithJobEndingAfterNextStarts_DetectsOverlapIfSignificant()
    {
        // Arrange - Job ends after next starts by more than 2 months
        var employmentHistory = new List<EmploymentEntry>
        {
            CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 10, 31)),
            CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
        };

        // Act
        var result = _sut.Analyse(employmentHistory);

        // Assert
        result.Overlaps.Should().HaveCount(1);
        result.Overlaps[0].Months.Should().BeGreaterThan(2);
    }

    #endregion

    #region Helper Methods

    private static EmploymentEntry CreateEmployment(string companyName, DateOnly? startDate, DateOnly? endDate)
    {
        return new EmploymentEntry
        {
            CompanyName = companyName,
            JobTitle = "Software Developer",
            StartDate = startDate,
            EndDate = endDate,
            IsCurrent = false
        };
    }

    private static EmploymentEntry CreateCurrentEmployment(string companyName, DateOnly startDate)
    {
        return new EmploymentEntry
        {
            CompanyName = companyName,
            JobTitle = "Software Developer",
            StartDate = startDate,
            EndDate = null,
            IsCurrent = true
        };
    }

    #endregion
}
