using JSMR.Application.Scanning.Contracts;
using JSMR.Infrastructure.Scanning.Extensions;
using Shouldly;

namespace JSMR.Tests.Unit;

public class DLSiteWorkExpectedDateInferenceTests
{
    [Fact]
    public void Infers_From_Sandwich_Expected_Dates()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work(),
            Work(expected: new DateOnly(2026, 5, 1)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1)
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void Infers_From_Sandwich_Sales_Dates_Early()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 2)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 7)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            new DateOnly(2026, 5, 1),
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void Infers_From_Sandwich_Sales_Dates_Middle()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 12)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 13)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            new DateOnly(2026, 5, 11),
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void Infers_From_Sandwich_Sales_Dates_Late()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 25)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 27)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            new DateOnly(2026, 5, 21),
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void Infers_From_Sandwich_Mixed_Dates()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 7)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void Infers_A_Run_Of_Missing_Items_When_Bounded_By_Same_Date()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work(),
            Work(),
            Work(),
            Work(expected: new DateOnly(2026, 5, 1)),
        ];

        DateOnly?[] expected =
        [
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 5, 1),
        ];

        AttemptInferAndVerify(works, expected);
    }

    [Fact]
    public void DoesNotInfer_When_Expected_Sandwich_Difference()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work(),
            Work(expected: new DateOnly(2026, 5, 11)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            new DateOnly(2026, 5, 1),
            null,
            new DateOnly(2026, 5, 11)
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNotInfer_When_Sales_Sandwich_Difference_Early_Middle()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 1)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 12)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            null,
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNotInfer_When_Sales_Sandwich_Difference_Early_Late()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 1)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 22)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            null,
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNotInfer_When_Sales_Sandwich_Difference_Middle_Late()
    {
        DLSiteWork[] works =
        [
            Work(sales: new DateOnly(2026, 5, 14)),
            Work(),
            Work(sales: new DateOnly(2026, 5, 22)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null, // Sales date will not have expected date
            null,
            null // Sales date will not have expected date
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNotInfer_When_No_Left()
    {
        DLSiteWork[] works =
        [
            Work(),
            Work(expected: new DateOnly(2026, 5, 1)),
        ];

        DateOnly?[] expectedExpectedDates =
        [
            null,
            new DateOnly(2026, 5, 1)
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNotInfer_When_No_Right()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work()
        ];

        DateOnly?[] expectedExpectedDates =
        [
            new DateOnly(2026, 5, 1),
            null
        ];

        AttemptInferAndVerify(works, expectedExpectedDates);
    }

    [Fact]
    public void DoesNot_Overwrite_Existing_ExpectedDate()
    {
        DLSiteWork[] works =
        [
            Work(expected: new DateOnly(2026, 5, 1)),
            Work(expected: new DateOnly(2026, 4, 11)), // already set
            Work(expected: new DateOnly(2026, 5, 21)),
        ];

        DateOnly?[] expected =
        [
            new DateOnly(2026, 5, 1),
            new DateOnly(2026, 4, 11),
            new DateOnly(2026, 5, 21),
        ];

        AttemptInferAndVerify(works, expected);
    }

    private static void AttemptInferAndVerify(DLSiteWork[] works, DateOnly?[] expectedExpectedDates)
    {
        // Act
        works.InferAndUpdateExpectedDates();

        // Assert
        works.Length.ShouldBe(expectedExpectedDates.Length);

        for (int i = 0; i < works.Length; i++)
            works[i].ExpectedDate.ShouldBe(expectedExpectedDates[i]);
    }

    private static DLSiteWork Work(DateOnly? expected = null, DateOnly? sales = null)
        => DLSiteWorkTestFactory.Create(expectedDate: expected, salesDate: sales);
}