using System.Text;
using MaBibli.Api.Services.Souhaits;
using MaBibli.Shared.Dtos;

namespace MaBibli.Tests;

/// <summary>
/// Contenu <b>exact</b> des fichiers d'export. Fonctions pures : rien à simuler.
/// </summary>
public class ExportSouhaitsTests
{
    private static readonly DateTime Jour = new(2026, 8, 18, 10, 0, 0, DateTimeKind.Utc);

    private static SouhaitDto Souhait(
        int id, string titre, string? auteur = null, string? editeur = null,
        string? annee = null, string? isbn = null, string? note = null) =>
        new()
        {
            Id = id,
            Titre = titre,
            Auteur = auteur,
            Editeur = editeur,
            Annee = annee,
            Isbn = isbn,
            Note = note,
            DateAjout = Jour,
        };

    [Fact]
    public void Le_texte_groupe_les_livres_par_auteur()
    {
        var texte = ExportSouhaits.Texte(
            [
                Souhait(1, "La prophétie des abeilles", "Bernard Werber"),
                Souhait(2, "Germinal", "Émile Zola"),
                Souhait(3, "Les fourmis", "Bernard Werber"),
            ],
            "Mathieu",
            Jour);

        Assert.Contains("Liste d'envies de Mathieu — 18/08/2026", texte);
        Assert.Contains("3 livres", texte);

        // Un seul en-tête par auteur, et ses deux titres regroupés dessous.
        var lignes = texte.Split('\n');
        Assert.Single(lignes, l => l == "Bernard Werber");

        var debutWerber = Array.IndexOf(lignes, "Bernard Werber");
        Assert.Equal("  - La prophétie des abeilles", lignes[debutWerber + 1]);
        Assert.Equal("  - Les fourmis", lignes[debutWerber + 2]);

        // Werber avant Zola : les auteurs sont classés alphabétiquement.
        Assert.True(debutWerber < Array.IndexOf(lignes, "Émile Zola"));
    }

    [Fact]
    public void Le_texte_range_les_livres_sans_auteur_en_dernier()
    {
        var texte = ExportSouhaits.Texte(
            [Souhait(1, "Titre orphelin"), Souhait(2, "Germinal", "Émile Zola")],
            null,
            Jour);

        Assert.True(texte.IndexOf("Émile Zola", StringComparison.Ordinal)
                    < texte.IndexOf("Auteur non précisé", StringComparison.Ordinal));
    }

    [Fact]
    public void Le_texte_porte_les_precisions_utiles_en_librairie()
    {
        var texte = ExportSouhaits.Texte(
            [Souhait(1, "Germinal", "Émile Zola", "Le Livre de poche", "1885", "2253004227", "en poche seulement")],
            null,
            Jour);

        Assert.Contains("  - Germinal (Le Livre de poche, 1885, ISBN 2253004227)", texte);
        Assert.Contains("    en poche seulement", texte);
    }

    [Fact]
    public void Le_texte_le_dit_quand_la_liste_est_vide()
    {
        var texte = ExportSouhaits.Texte([], "Mathieu", Jour);

        Assert.Contains("(aucun livre dans la liste)", texte);
    }

    [Fact]
    public void Le_csv_porte_un_en_tete_et_une_ligne_par_envie()
    {
        var csv = ExportSouhaits.Csv([Souhait(1, "Germinal", "Émile Zola", "Le Livre de poche", "1885")]);

        var lignes = csv.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);

        Assert.Equal("Titre;Auteur;Éditeur;Année;ISBN;Note;Ajouté le", lignes[0]);
        Assert.Equal("Germinal;Émile Zola;Le Livre de poche;1885;;;2026-08-18", lignes[1]);
    }

    /// <summary>
    /// Sans échappement, une note contenant le séparateur décalerait toutes les colonnes
    /// suivantes — et une note est du texte parfaitement libre.
    /// </summary>
    [Fact]
    public void Le_csv_echappe_le_separateur_et_les_guillemets()
    {
        var csv = ExportSouhaits.Csv(
            [Souhait(1, "Titre; piégé", note: "il a dit \"oui\"")]);

        var ligne = csv.Split("\r\n")[1];

        Assert.StartsWith("\"Titre; piégé\";", ligne);
        Assert.Contains("\"il a dit \"\"oui\"\"\"", ligne);

        // Sept colonnes une fois l'échappement respecté, pas huit.
        Assert.Equal(7, ColonnesCsv(ligne).Count);
    }

    [Fact]
    public void Le_csv_echappe_les_sauts_de_ligne()
    {
        var csv = ExportSouhaits.Csv([Souhait(1, "Titre", note: "ligne 1\nligne 2")]);

        Assert.Contains("\"ligne 1\nligne 2\"", csv);
    }

    /// <summary>
    /// Sans BOM, Excel affiche « Ã‰mile Zola » — sur un fonds français, à peu près chaque ligne.
    /// </summary>
    [Fact]
    public void Le_bom_precede_le_csv()
    {
        var octets = ExportSouhaits.Bom
            .Concat(Encoding.UTF8.GetBytes(ExportSouhaits.Csv([Souhait(1, "Émile")])))
            .ToArray();

        Assert.Equal([0xEF, 0xBB, 0xBF], octets[..3]);
    }

    [Theory]
    [InlineData(FormatExportSouhaits.Texte, "liste-envies-2026-08-18.txt", "text/plain; charset=utf-8")]
    [InlineData(FormatExportSouhaits.Csv, "liste-envies-2026-08-18.csv", "text/csv; charset=utf-8")]
    public void Le_fichier_est_date_et_typé(FormatExportSouhaits format, string nom, string mime)
    {
        Assert.Equal(nom, ExportSouhaits.NomFichier(format, Jour));
        Assert.Equal(mime, ExportSouhaits.TypeMime(format));
    }

    /// <summary>Découpe une ligne CSV en respectant les guillemets (lecteur minimal, pour le test).</summary>
    private static List<string> ColonnesCsv(string ligne)
    {
        var colonnes = new List<string>();
        var courante = new StringBuilder();
        var dansGuillemets = false;

        for (var i = 0; i < ligne.Length; i++)
        {
            var c = ligne[i];

            if (c == '"')
            {
                if (dansGuillemets && i + 1 < ligne.Length && ligne[i + 1] == '"')
                {
                    courante.Append('"');
                    i++;
                    continue;
                }

                dansGuillemets = !dansGuillemets;
                continue;
            }

            if (c == ExportSouhaits.SeparateurCsv && !dansGuillemets)
            {
                colonnes.Add(courante.ToString());
                courante.Clear();
                continue;
            }

            courante.Append(c);
        }

        colonnes.Add(courante.ToString());
        return colonnes;
    }
}
