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

namespace MaBibli.Tests;

/// <summary>
/// Codes-barres de périodiques (préfixe <c>977</c>) : reconnaissance, dérivation de l'ISSN,
/// et lecture de la notice BnF correspondante.
/// </summary>
public class CodePeriodiqueTests
{
    /// <summary>
    /// Le cas remonté en usage réel : ce code renvoyait « aucun résultat », alors qu'il
    /// n'a jamais pu décrire un livre.
    /// </summary>
    private const string EanMedor = "9772466671438";

    [Theory]
    [InlineData(EanMedor, true)]
    [InlineData("9782070612758", false)]  // Le Petit Prince : un vrai livre
    [InlineData("9791234567896", false)]  // préfixe 979, livre lui aussi
    [InlineData("2253004227", false)]     // ISBN-10 : trop court pour porter un préfixe
    [InlineData("9772466671437", false)]  // bon préfixe, clé EAN fausse
    [InlineData(null, false)]
    [InlineData("", false)]
    public void Reconnait_les_codes_de_periodique(string? ean, bool attendu)
        => Assert.Equal(attendu, CodePeriodique.EstCodePeriodique(ean));

    /// <summary>
    /// La clé de l'ISSN n'est <b>pas</b> dans le code-barres : elle se recalcule. Vérifié
    /// contre la BnF le 2026-08-18 — l'ISSN 2466-6718 y désigne bien « Médor ».
    /// </summary>
    [Fact]
    public void Deduit_lIssn_du_code_barres()
        => Assert.Equal("2466-6718", CodePeriodique.IssnDepuis(EanMedor));

    [Fact]
    public void Ne_deduit_rien_dun_code_de_livre()
        => Assert.Null(CodePeriodique.IssnDepuis("9782070612758"));

    /// <summary>
    /// Les deux chiffres qui précèdent la clé encodent la parution : deux numéros d'une même
    /// revue doivent donner le <b>même</b> ISSN, sinon chaque magazine créerait une fiche.
    /// </summary>
    [Fact]
    public void Deux_parutions_de_la_meme_revue_donnent_le_meme_Issn()
    {
        var issns = new[] { "9772466671438", "9772466671391" }
            .Select(CodePeriodique.IssnDepuis)
            .ToList();

        Assert.Equal("2466-6718", issns[0]);
        Assert.Equal(issns[0], issns[1]);
    }

    [Fact]
    public void Notice_BnF_de_periodique_est_lue_et_nettoyee()
    {
        var revue = BnfSruParser.ParserPeriodique(
            Fixture.Lire("bnf-issn-2466-6718.xml"), "2466-6718");

        Assert.NotNull(revue);

        // « Médor (Bruxelles) » : le parenthésé est un qualificatif de désambiguïsation,
        // pas une partie du nom écrit sur la couverture.
        Assert.Equal("Médor", revue!.Titre);
        Assert.Equal("2466-6718", revue.Issn);
        Assert.Equal("2015", revue.Annee);
    }

    [Fact]
    public void Reponse_sans_notice_ne_produit_pas_de_titre_invente()
    {
        var vide = """
            <srw:searchRetrieveResponse xmlns:srw="http://www.loc.gov/zing/srw/">
              <srw:numberOfRecords>0</srw:numberOfRecords>
            </srw:searchRetrieveResponse>
            """;

        Assert.Null(BnfSruParser.ParserPeriodique(vide, "2466-6718"));
    }

    // ── La cascade ne doit pas partir du tout ────────────────────────────────

    /// <summary>
    /// Client BnF simulé : compte les appels ISBN pour prouver qu'aucun n'a lieu.
    /// </summary>
    private sealed class BnfFake(PeriodiqueDetecte? revue) : IBnfClient
    {
        public List<string> IsbnInterroges { get; } = [];

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

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

        public Task<(ResultatBibliographie, string?)> RechercherParAuteurAsync(
            string auteur, CancellationToken ct = default) =>
            Task.FromResult((new ResultatBibliographie(), (string?)null));

        public Task<(PeriodiqueDetecte?, string?)> RechercherPeriodiqueAsync(
            string issn, CancellationToken ct = default)
        {
            IssnInterroges.Add(issn);
            return Task.FromResult((revue, (string?)null));
        }

        /// <summary>Recherche par titre : couverte par ses propres tests.</summary>
        public Task<(IReadOnlyList<CandidatLivre>, string?)> RechercherParTitreAsync(
            string titre, string? auteur, CancellationToken ct = default) =>
            Task.FromResult(((IReadOnlyList<CandidatLivre>)[], (string?)null));
    }

    private sealed class OpenLibraryFake : IOpenLibraryClient
    {
        public int Appels { get; private set; }

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

    /// <summary>
    /// Le point du bug : la cascade s'exécutait en entier pour finir sur « aucun résultat ».
    /// Elle ne doit désormais pas partir — seul l'ISSN est interrogé.
    /// </summary>
    [Fact]
    public async Task Un_code_977_ne_declenche_aucune_recherche_de_livre()
    {
        var bnf = new BnfFake(new PeriodiqueDetecte { Issn = "2466-6718", Titre = "Médor" });
        var ol = new OpenLibraryFake();
        var service = new IsbnLookupService(bnf, ol, NullLogger<IsbnLookupService>.Instance);

        var resultat = await service.RechercherAsync(EanMedor);

        Assert.NotNull(resultat);
        Assert.Empty(bnf.IsbnInterroges);
        Assert.Equal(0, ol.Appels);
        Assert.Equal(["2466-6718"], bnf.IssnInterroges);
        Assert.Empty(resultat!.Candidats);
        Assert.Equal("Médor", resultat.Periodique?.Titre);
    }

    /// <summary>
    /// BnF muette : on connaît quand même l'ISSN, puisqu'il se déduit du code-barres.
    /// Ne rien renvoyer ferait retomber l'écran sur « aucun résultat », le bug d'origine.
    /// </summary>
    [Fact]
    public async Task BnF_muette_laisse_quand_meme_lIssn()
    {
        var service = new IsbnLookupService(
            new BnfFake(null), new OpenLibraryFake(), NullLogger<IsbnLookupService>.Instance);

        var resultat = await service.RechercherAsync(EanMedor);

        Assert.Equal("2466-6718", resultat?.Periodique?.Issn);
        Assert.Null(resultat?.Periodique?.Titre);
    }

    /// <summary>Un livre ordinaire n'est jamais pris pour un périodique.</summary>
    [Fact]
    public async Task Un_ISBN_de_livre_passe_par_la_cascade_normale()
    {
        var bnf = new BnfFake(null);
        var service = new IsbnLookupService(
            bnf, new OpenLibraryFake(), NullLogger<IsbnLookupService>.Instance);

        var resultat = await service.RechercherAsync("9782070612758");

        Assert.Null(resultat?.Periodique);
        Assert.Empty(bnf.IssnInterroges);
        Assert.NotEmpty(bnf.IsbnInterroges);
    }
}
