using Kavita.Models.DTOs.Progress;
using Kavita.Services.Helpers;

namespace Kavita.Services.Tests.Helpers;

public class KoreaderHelperTests
{
    #region UpdateProgressDto Tests

    [Theory]
    [InlineData("/body/DocFragment[11]/body/div/a", 10, null)] // Anchor tags return null BookScrollId
    [InlineData("/body/DocFragment[1]/body/div/p[40]", 0, "//body/div/p[40]")]
    [InlineData("/body/DocFragment[5]/body/section/div[2]", 4, "//body/section/div[2]")]
    public void UpdateProgressDto_StandardXPath(string koreaderPosition, int expectedPage, string? expectedScrollId)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
        Assert.Equal(expectedScrollId, actual.BookScrollId);
    }

    [Theory]
    [InlineData("/body/DocFragment[8]/body/div/p[28]/text().264", 7, "//body/div/p[28]")]
    [InlineData("/body/DocFragment[3]/body/h1/text().0", 2, "//body/h1")]
    [InlineData("/body/DocFragment[9]/body/p[52]/text().248", 8, "//body/p[52]")]
    [InlineData("/body/DocFragment[6]/body/div/span.0", 5, "//body/div/span")] // Trailing .0 stripped
    public void UpdateProgressDto_WithTextOffsets(string koreaderPosition, int expectedPage, string? expectedScrollId)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
        Assert.Equal(expectedScrollId, actual.BookScrollId);
    }

    [Theory]
    [InlineData("/body/DocFragment[10].0", 9, null)] // Short path - no scroll ID determinable
    [InlineData("/body/DocFragment[5]", 4, null)]
    [InlineData("/body/DocFragment[1]/body", 0, null)] // Too short for full path extraction
    public void UpdateProgressDto_ShortPaths(string koreaderPosition, int expectedPage, string? expectedScrollId)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
        Assert.Equal(expectedScrollId, actual.BookScrollId);
    }

    [Theory]
    [InlineData("#_doc_fragment_10", 9, null)]
    [InlineData("#_doc_fragment_1", 0, null)]
    [InlineData("#_doc_fragment_10_ some_anchor", 9, null)] // With trailing anchor
    [InlineData("#_doc_fragment10", 9, null)] // Legacy format without underscore
    [InlineData("#_doc_fragment1", 0, null)]
    public void UpdateProgressDto_HashFragmentFormat(string koreaderPosition, int expectedPage, string? expectedScrollId)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
        Assert.Equal(expectedScrollId, actual.BookScrollId);
    }

    [Theory]
    [InlineData("5", 4)] // Archive/PDF page number (1-indexed from KOReader)
    [InlineData("1", 0)]
    [InlineData("100", 99)]
    public void UpdateProgressDto_NumericOnly(string koreaderPosition, int expectedPage)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
    }

    [Theory]
    [InlineData("/body/DocFragment[11]/body/id(\"chapter1\")", 10, null)] // id() selectors not supported
    [InlineData("/body/DocFragment[5]/body/div/id(\"section2\")/p[1]", 4, null)]
    public void UpdateProgressDto_IdSelectors(string koreaderPosition, int expectedPage, string? expectedScrollId)
    {
        var actual = EmptyProgressDto();

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition);

        Assert.Equal(expectedPage, actual.PageNum);
        Assert.Equal(expectedScrollId, actual.BookScrollId);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void UpdateProgressDto_EmptyOrNull_NoChanges(string? koreaderPosition)
    {
        var actual = EmptyProgressDto();
        actual.PageNum = 5;
        actual.BookScrollId = "//body/p[1]";

        KoreaderHelper.UpdateProgressDto(actual, koreaderPosition!);

        // Should remain unchanged
        Assert.Equal(5, actual.PageNum);
        Assert.Equal("//body/p[1]", actual.BookScrollId);
    }

    #endregion

    #region GetKoreaderPosition Tests

    [Theory]
    [InlineData("//body/p[20]", 4, "/body/DocFragment[5]/body/p[20].0")]
    [InlineData("//body/div/section[3]", 0, "/body/DocFragment[1]/body/div/section[3].0")]
    [InlineData("//body/h1", 9, "/body/DocFragment[10]/body/h1.0")]
    public void GetKoreaderPosition_WithScrollId(string? scrollId, int page, string expectedPosition)
    {
        var given = EmptyProgressDto();
        given.BookScrollId = scrollId;
        given.PageNum = page;

        var result = KoreaderHelper.GetKoreaderPosition(given);

        Assert.Equal(expectedPosition, result, ignoreCase: true);
    }

    [Theory]
    [InlineData(null, 9, "/body/DocFragment[10].0")]
    [InlineData("", 0, "/body/DocFragment[1].0")]
    [InlineData(null, 4, "/body/DocFragment[5].0")]
    public void GetKoreaderPosition_NoScrollId_ReturnsFragmentOnly(string? scrollId, int page, string expectedPosition)
    {
        var given = EmptyProgressDto();
        given.BookScrollId = scrollId;
        given.PageNum = page;

        var result = KoreaderHelper.GetKoreaderPosition(given);

        Assert.Equal(expectedPosition, result, ignoreCase: true);
    }

    [Theory]
    [InlineData("id(\"chapter1\")", 5, "/body/DocFragment[6].0")]
    [InlineData("id(\"h2\")", 10, "/body/DocFragment[11].0")]
    public void GetKoreaderPosition_IdSelector_ReturnsFragmentOnly(string scrollId, int page, string expectedPosition)
    {
        var given = EmptyProgressDto();
        given.BookScrollId = scrollId;
        given.PageNum = page;

        var result = KoreaderHelper.GetKoreaderPosition(given);

        Assert.Equal(expectedPosition, result, ignoreCase: true);
    }

    #endregion

    #region HashContents Tests

    [Theory]
    [InlineData("./Data/AesopsFables.epub", "8795ACA4BF264B57C1EEDF06A0CEE688")]
    public void HashContents_ValidFile_ReturnsExpectedHash(string filePath, string expectedHash)
    {
        Assert.Equal(expectedHash, KoreaderHelper.HashContents(filePath));
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("./Data/NonExistent.epub")]
    public void HashContents_InvalidFile_ReturnsNull(string? filePath)
    {
        Assert.Null(KoreaderHelper.HashContents(filePath!));
    }

    #endregion

    private static ProgressDto EmptyProgressDto()
    {
        return new ProgressDto
        {
            ChapterId = 0,
            PageNum = 0,
            VolumeId = 0,
            SeriesId = 0,
            LibraryId = 0
        };
    }
}
