using MaBibli.Api.Services.Isbn;
using MaBibli.Shared.Dtos;
using Microsoft.Extensions.Logging.Abstractions;

namespace MaBibli.Tests;

/// <summary>
/// Enchaînement de la cascade BnF (ISBN-13) → BnF (ISBN-10) → OpenLibrary,
/// avec des sources simulées : aucun accès réseau.
/// </summary>
public class IsbnLookupServiceTests
{
    private sealed class BnfFake : IBnfClient
    {
        public required Func<string, IReadOnlyList<CandidatLivre>> Reponse { get; init; }

        public List<string> IsbnInterroges { get; } = [];

        public string? Avertissement { get; init; }

        public Task<(IReadOnlyList<CandidatLivre>, string?)> RechercherAsync(
            string isbn, string? urlCouverture, CancellationToken ct = default)
        {
            IsbnInterroges.Add(isbn);
            return Task.FromResult((Reponse(isbn), Avertissement));
        }

        /// <summary>
        /// Ces tests ne portent que sur la cascade ISBN : la recherche par auteur n'y intervient
        /// pas, mais l'interface l'exige. Elle est couverte par BnfBibliographieParserTests.
        /// </summary>
        public Task<(ResultatBibliographie, string?)> RechercherParAuteurAsync(
            string auteur, CancellationToken ct = default) =>
            Task.FromResult((new ResultatBibliographie(), (string?)null));
    }

    private sealed class OpenLibraryFake : IOpenLibraryClient
    {
        public required Func<string, IReadOnlyList<CandidatLivre>> Reponse { get; init; }

        public List<string> IsbnInterroges { get; } = [];

        public Task<(IReadOnlyList<CandidatLivre>, string?)> RechercherAsync(
            string isbn, string? urlCouverture, CancellationToken ct = default)
        {
            IsbnInterroges.Add(isbn);
            return Task.FromResult((Reponse(isbn), (string?)null));
        }
    }

    private static CandidatLivre Notice(SourceMetadonnees source, string isbn) =>
        new() { Titre = "Un titre", Source = source, IsbnInterroge = isbn };

    private static IsbnLookupService Service(IBnfClient bnf, IOpenLibraryClient ol) =>
        new(bnf, ol, NullLogger<IsbnLookupService>.Instance);

    [Fact]
    public async Task Sarrete_a_la_BnF_quand_lISBN13_suffit()
    {
        var bnf = new BnfFake { Reponse = isbn => [Notice(SourceMetadonnees.Bnf, isbn)] };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        var r = await Service(bnf, ol).RechercherAsync("978-2-07-061275-8");

        Assert.NotNull(r);
        Assert.Equal("9782070612758", r.IsbnDemande);
        Assert.Equal("2070612759", r.Isbn10);
        Assert.Equal(["9782070612758"], bnf.IsbnInterroges);
        Assert.Empty(ol.IsbnInterroges);
        Assert.Single(r.Candidats);
    }

    [Fact]
    public async Task Reessaie_la_BnF_avec_lISBN10_converti()
    {
        // Le cas Germinal : rien en ISBN-13, tout en ISBN-10.
        var bnf = new BnfFake
        {
            Reponse = isbn => isbn == "2253004227"
                ? [Notice(SourceMetadonnees.Bnf, isbn), Notice(SourceMetadonnees.Bnf, isbn)]
                : [],
        };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        var r = await Service(bnf, ol).RechercherAsync("9782253004226");

        Assert.Equal(["9782253004226", "2253004227"], bnf.IsbnInterroges);
        Assert.Empty(ol.IsbnInterroges);
        Assert.Equal(2, r!.Candidats.Count);
        Assert.All(r.Candidats, c => Assert.Equal("2253004227", c.IsbnInterroge));
    }

    [Fact]
    public async Task Bascule_sur_OpenLibrary_quand_la_BnF_ignore_les_deux_formes()
    {
        var bnf = new BnfFake { Reponse = _ => [] };
        var ol = new OpenLibraryFake
        {
            Reponse = isbn => [Notice(SourceMetadonnees.OpenLibrary, isbn)],
        };

        var r = await Service(bnf, ol).RechercherAsync("9780262033848");

        Assert.Equal(["9780262033848", "0262033844"], bnf.IsbnInterroges);
        // OpenLibrary est interrogée avec l'ISBN-13, la forme qu'elle indexe.
        Assert.Equal(["9780262033848"], ol.IsbnInterroges);
        Assert.Equal(SourceMetadonnees.OpenLibrary, Assert.Single(r!.Candidats).Source);
    }

    [Fact]
    public async Task Une_BnF_en_panne_ne_fait_pas_echouer_la_cascade()
    {
        var bnf = new BnfFake { Reponse = _ => [], Avertissement = "BnF injoignable" };
        var ol = new OpenLibraryFake { Reponse = isbn => [Notice(SourceMetadonnees.OpenLibrary, isbn)] };

        var r = await Service(bnf, ol).RechercherAsync("9782253004226");

        Assert.Single(r!.Candidats);
        Assert.Equal(2, r.Avertissements.Count);  // une par tentative BnF
    }

    [Fact]
    public async Task Aucune_source_ne_trouve_rien()
    {
        var bnf = new BnfFake { Reponse = _ => [] };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        var r = await Service(bnf, ol).RechercherAsync("9782253004226");

        Assert.NotNull(r);
        Assert.Empty(r.Candidats);
        Assert.Empty(r.Avertissements);
    }

    [Fact]
    public async Task ISBN_en_979_ninterroge_la_BnF_quune_seule_fois()
    {
        var bnf = new BnfFake { Reponse = _ => [] };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        var r = await Service(bnf, ol).RechercherAsync("9791023507027");

        Assert.Null(r!.Isbn10);
        Assert.Equal(["9791023507027"], bnf.IsbnInterroges);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("pas-un-isbn")]
    [InlineData("9782253004225")]  // clé fausse
    [InlineData("123")]
    public async Task ISBN_invalide_renvoie_null(string entree)
    {
        var bnf = new BnfFake { Reponse = _ => [] };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        Assert.Null(await Service(bnf, ol).RechercherAsync(entree));
        Assert.Empty(bnf.IsbnInterroges);
        Assert.Empty(ol.IsbnInterroges);
    }

    [Fact]
    public async Task La_couverture_OpenLibrary_est_transmise_meme_pour_une_notice_BnF()
    {
        string? couvertureVue = null;
        var bnf = new BnfFake { Reponse = _ => [] };
        var ol = new OpenLibraryFake { Reponse = _ => [] };

        // On capture l'URL passée à la BnF via un client dédié.
        var bnfCapture = new CaptureCouverture();
        var r = await Service(bnfCapture, ol).RechercherAsync("9782253004226");
        couvertureVue = bnfCapture.DerniereCouverture;

        Assert.NotNull(r);
        Assert.Equal(
            "https://covers.openlibrary.org/b/isbn/9782253004226-L.jpg?default=false",
            couvertureVue);
        Assert.Empty(bnf.IsbnInterroges);
    }

    private sealed class CaptureCouverture : IBnfClient
    {
        public string? DerniereCouverture { get; private set; }

        public Task<(IReadOnlyList<CandidatLivre>, string?)> RechercherAsync(
            string isbn, string? urlCouverture, CancellationToken ct = default)
        {
            DerniereCouverture = urlCouverture;
            return Task.FromResult(((IReadOnlyList<CandidatLivre>)[], (string?)null));
        }

        /// <summary>
        /// Ces tests ne portent que sur la cascade ISBN : la recherche par auteur n'y intervient
        /// pas, mais l'interface l'exige. Elle est couverte par BnfBibliographieParserTests.
        /// </summary>
        public Task<(ResultatBibliographie, string?)> RechercherParAuteurAsync(
            string auteur, CancellationToken ct = default) =>
            Task.FromResult((new ResultatBibliographie(), (string?)null));
    }
}
