using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Validation;
using GerbilManagerWebAPI.Contracts;

namespace GerbilManager.Tests;

/// <summary>
/// FEAT-13: Tests des Abgabevertrag-Generators. Die erzeugte .docx wird als
/// Zip geöffnet und auf word/document.xml geprüft — kein Word nötig.
/// </summary>
public class ContractGeneratorTests
{
    private static BreederProfile Seller() => new()
    {
        ZuchtName = "Zucht Testhausen",
        Name = "Frau Erika Muster",
        Address = "Musterweg 1, 12345 Testhausen",
        Phone = "0123 456789",
        Email = "zucht@example.org",
        Homepage = "https://zucht.example.org/",
        City = "Testhausen",
    };

    private static ContractAnimal Krümel() => new(
        Name: "Krümel",
        Geschlecht: "Weiblich",
        Geburtsdatum: new DateOnly(2025, 3, 9),
        Farbschlag: "Agouti");

    private static ContractData OneAnimal() => new(
        Seller: Seller(),
        Buyer: new ContractBuyer(
            Name: "Herr Max Beispiel",
            Address: "Beispielallee 7, 54321 Beispielstadt",
            Phone: "0987 654321",
            Email: "max@example.com"),
        Animals: [Krümel()],
        Price: 72m,
        HandoverDate: new DateOnly(2026, 6, 5));

    /// <summary>
    /// Sichtbarer Text aus word/document.xml der erzeugten Datei. Tags werden
    /// entfernt, damit Aussagen über Run-Grenzen hinweg möglich sind (Werte
    /// und Fließtext liegen in getrennten w:t-Runs).
    /// </summary>
    private static string DocumentText(byte[] docx)
    {
        using var zip = new ZipArchive(new MemoryStream(docx), ZipArchiveMode.Read);
        var entry = Assert.Single(zip.Entries, e => e.FullName == "word/document.xml");
        using var reader = new StreamReader(entry.Open(), Encoding.UTF8);
        return Regex.Replace(reader.ReadToEnd(), "<[^>]+>", "");
    }

    [Fact]
    public void EinTier_EnthaeltAlleVertragswerte()
    {
        var text = DocumentText(ContractGenerator.Generate(OneAnimal()));

        // Verkäufer-Block (aus dem Zuchtprofil)
        Assert.Contains("Zucht Testhausen", text);
        Assert.Contains("Frau Erika Muster", text);
        Assert.Contains("Musterweg 1, 12345 Testhausen", text);
        Assert.Contains("0123 456789", text);
        Assert.Contains("zucht@example.org", text);
        Assert.Contains("https://zucht.example.org/", text);

        // Käufer-Block
        Assert.Contains("Herr Max Beispiel", text);
        Assert.Contains("Beispielallee 7, 54321 Beispielstadt", text);
        Assert.Contains("0987 654321", text);
        Assert.Contains("max@example.com", text);

        // Tier
        Assert.Contains("Krümel", text);
        Assert.Contains("Weiblich", text);
        Assert.Contains("09.03.2025", text);
        Assert.Contains("Agouti", text);

        // Kaufpreis (de-DE) + Übergabedatum (TT.MM.JJJJ)
        Assert.Contains("Kaufpreis von 72,00 €", text);
        Assert.Contains("am 05.06.2026 dem Käufer", text);

        // Unterschriftszeile: Ort des Züchters + Vertragsdatum (= Übergabedatum)
        Assert.Contains("Testhausen, den 05.06.2026", text);
    }

    [Fact]
    public void KeineUnersetztenTokens()
    {
        var text = DocumentText(ContractGenerator.Generate(OneAnimal()));
        Assert.DoesNotContain("{{", text);
        Assert.DoesNotContain("}}", text);
    }

    [Fact]
    public void DreiTiere_KlontDieTierTabelleProTier()
    {
        var data = OneAnimal() with
        {
            Animals =
            [
                Krümel(),
                new ContractAnimal("Fridolin", "Männlich", new DateOnly(2023, 5, 1), "Schwarz"),
                new ContractAnimal("Luna", "Weiblich", new DateOnly(2023, 8, 15), "Gold"),
            ],
        };

        var text = DocumentText(ContractGenerator.Generate(data));

        Assert.Contains("Krümel", text);
        Assert.Contains("Fridolin", text);
        Assert.Contains("Luna", text);
        Assert.Contains("01.05.2023", text);
        Assert.Contains("15.08.2023", text);

        // Pro Tier eine Tabelle: „Tierart:“ ist das fixe Label jeder Tier-Tabelle.
        Assert.Equal(3, Regex.Matches(text, "Tierart:").Count);
        Assert.DoesNotContain("{{", text);
    }

    [Fact]
    public void PreisUndDatum_DeutscheFormate()
    {
        var data = OneAnimal() with { Price = 1234.5m, HandoverDate = new DateOnly(2026, 1, 3) };
        var text = DocumentText(ContractGenerator.Generate(data));

        Assert.Contains("Kaufpreis von 1.234,50 €", text);
        Assert.Contains("am 03.01.2026 dem Käufer", text);
    }

    [Fact]
    public void VertragsDatum_UeberschreibtUebergabedatumInDerUnterschriftszeile()
    {
        var data = OneAnimal() with { ContractDate = new DateOnly(2026, 6, 7) };
        var text = DocumentText(ContractGenerator.Generate(data));

        Assert.Contains("Testhausen, den 07.06.2026", text);
        Assert.Contains("am 05.06.2026 dem Käufer", text); // Übergabedatum bleibt eigenständig
    }

    [Fact]
    public void OptionaleFelder_LeerStattPlatzhalter()
    {
        var data = OneAnimal() with
        {
            Buyer = new ContractBuyer("Frau Lisa Test", "Testgasse 2, 11111 Teststadt"),
            Animals = [new ContractAnimal("Momo", "Unbekannt")],
        };

        var text = DocumentText(ContractGenerator.Generate(data));

        Assert.Contains("Frau Lisa Test", text);
        Assert.Contains("Momo", text);
        Assert.DoesNotContain("{{", text);
        Assert.DoesNotContain("null", text, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void OhneTiere_WirftArgumentException()
    {
        var data = OneAnimal() with { Animals = [] };
        var ex = Assert.Throws<ArgumentException>(() => ContractGenerator.Generate(data));
        Assert.Contains("mindestens ein Tier", ex.Message);
    }

    [Fact]
    public void ErzeugteDatei_IstEinGueltigesDocxPaket()
    {
        var docx = ContractGenerator.Generate(OneAnimal());

        using var zip = new ZipArchive(new MemoryStream(docx), ZipArchiveMode.Read);
        Assert.Contains(zip.Entries, e => e.FullName == "[Content_Types].xml");
        Assert.Contains(zip.Entries, e => e.FullName == "word/document.xml");
        // Tierfotos wurden beim Vorlagenbau entfernt, das Zucht-Logo bleibt.
        Assert.Contains(zip.Entries, e => e.FullName == "word/media/image1.jpeg");
        Assert.DoesNotContain(zip.Entries, e => e.FullName == "word/media/image2.jpeg");
    }

    [Fact]
    public void ErzeugtesDokument_ValidiertGegenOpenXmlSchema()
    {
        // Office-2013-Schema: die Vorlage nutzt w:tblLook-Attribute der
        // Word-2010-Form, die der 2007-Default des Validators nicht kennt.
        var validator = new OpenXmlValidator(DocumentFormat.OpenXml.FileFormatVersions.Office2013);

        var data = OneAnimal() with
        {
            Animals = [Krümel(), new ContractAnimal("Fridolin", "Männlich"), new ContractAnimal("Luna", "Weiblich")],
        };
        using var generatedDoc = WordprocessingDocument.Open(
            new MemoryStream(ContractGenerator.Generate(data)), isEditable: false);
        var errors = validator.Validate(generatedDoc)
            .Select(e => $"{e.ErrorType}: {e.Description} [{e.Path?.XPath}]")
            .ToList();

        Assert.True(errors.Count == 0, "OpenXml-Validierungsfehler:\n" + string.Join("\n", errors));
    }

    [Fact]
    public void VorlageSelbst_EnthaeltKeineEchtdaten()
    {
        // Die Vorlage wird hier über den Generator-Pfad geladen: ein Vertrag
        // mit leeren Werten darf keinerlei Daten des Mustervertrags enthalten.
        var data = new ContractData(
            new BreederProfile(),
            new ContractBuyer("", ""),
            [new ContractAnimal("", "")],
            0m,
            new DateOnly(2026, 1, 1));

        var text = DocumentText(ContractGenerator.Generate(data));

        // Stichproben der Original-PII (Name/Ort/Mail des Mustervertrags).
        Assert.DoesNotContain("Nießner", text);
        Assert.DoesNotContain("Hartengrund", text);
        Assert.DoesNotContain("Ronneburg", text);
        Assert.DoesNotContain("Schädtler", text);
        Assert.DoesNotContain("gmx.de", text);
        Assert.DoesNotContain("jimdofree", text);
    }
}
