using System.IO.Compression;
using System.Text;
using System.Text.Json;
using GerbilManagerWebAPI.Export;
using GerbilManagerWebAPI.Models;
using Microsoft.Extensions.DependencyInjection;

namespace GerbilManager.Tests
{
    /// <summary>
    /// EXPORT-1: CSV-Escaping-Units + voller Round-Trip über GET /export
    /// (seed -> Zip herunterladen -> Einträge parsen -> Zähler + Beispielzeile).
    /// </summary>
    public class ExportTests
    {
        // ── CSV-Escaping ───────────────────────────────────────────────────

        [Theory]
        [InlineData(null, "")]
        [InlineData("", "")]
        [InlineData("Krümel", "Krümel")] // Umlaute bleiben unquotiert erhalten
        [InlineData("a;b", "\"a;b\"")] // Semikolon = Trennzeichen -> quoten
        [InlineData("sagt \"hallo\"", "\"sagt \"\"hallo\"\"\"")] // Anführungszeichen verdoppeln
        [InlineData("Zeile1\nZeile2", "\"Zeile1\nZeile2\"")] // Zeilenumbruch -> quoten
        [InlineData("Meier, Hans", "Meier, Hans")] // Komma ist KEIN Trennzeichen (Semikolon-CSV)
        public void Escape_behandelt_Sonderfälle(string? input, string expected)
            => Assert.Equal(expected, ExportService.Escape(input));

        [Fact]
        public void BuildCsv_schreibt_Kopfzeile_und_quotierte_Zeilen()
        {
            var csv = ExportService.BuildCsv(
                ["Name", "Notizen"],
                [["Krümel", "frisst; gerne \"Hirse\""], ["Bo", null]]);

            var lines = csv.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
            Assert.Equal("Name;Notizen", lines[0]);
            Assert.Equal("Krümel;\"frisst; gerne \"\"Hirse\"\"\"", lines[1]);
            Assert.Equal("Bo;", lines[2]);
        }

        // ── Round-Trip über die API ────────────────────────────────────────

        [Fact]
        public async Task Export_liefert_Zip_mit_allen_Einträgen_und_korrekten_Daten()
        {
            using var factory = new ApiFactory();

            // Seed: Kontakt, Becken, Wurf, Tier (mit Bezügen), Gesundheit, Gewicht
            Guid gerbilId;
            using (var scope = factory.Services.CreateScope())
            {
                var db = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
                var contact = new Contact { Id = Guid.NewGuid(), Name = "Zoohandlung; \"Meier\"", Email = "meier@example.de" };
                var enclosure = new Enclosure { Id = Guid.NewGuid(), Name = "Großbecken" };
                var litter = new Litter { Id = Guid.NewGuid(), Name = "Wurf K", Date = new DateOnly(2025, 3, 12), TotalBorn = 5 };
                var colorVariety = db.ColorVarieties.First(); // HasData-Seed
                var gerbil = new Gerbil
                {
                    Id = Guid.NewGuid(),
                    Name = "Krümel",
                    Gender = Gender.female,
                    Status = GerbilStatus.Active,
                    DateOfBirth = new DateOnly(2025, 3, 12),
                    LitterId = litter.Id,
                    EnclosureId = enclosure.Id,
                    OriginContactId = contact.Id,
                    ColorVarietyId = colorVariety.Id,
                    Genotype = "Aa CC Dd EE GG Pp Spsp rere",
                    ImportSource = "Stammbaum von Akio Kids.xlsx",
                };
                gerbilId = gerbil.Id;
                db.AddRange(contact, enclosure, litter, gerbil,
                    new HealthRecord
                    {
                        Id = Guid.NewGuid(), GerbilId = gerbil.Id, Date = new DateOnly(2026, 1, 15),
                        Type = HealthRecordType.Vaccination, Description = "Jahresimpfung",
                        CreatedAt = DateTimeOffset.UtcNow,
                    },
                    new WeightRecord
                    {
                        Id = Guid.NewGuid(), GerbilId = gerbil.Id,
                        Date = new DateOnly(2026, 5, 1), WeightGrams = 78,
                    });
                await db.SaveChangesAsync();
            }

            var client = factory.CreateClient();
            var response = await client.GetAsync("/export");
            response.EnsureSuccessStatusCode();
            Assert.Equal("application/zip", response.Content.Headers.ContentType?.MediaType);
            Assert.Contains("rennmaus-export-", response.Content.Headers.ContentDisposition?.FileName);

            using var zip = new ZipArchive(await response.Content.ReadAsStreamAsync(), ZipArchiveMode.Read);
            string[] expectedEntries =
                ["export.json", "tiere.csv", "wuerfe.csv", "kontakte.csv",
                 "gesundheit.csv", "gewichte.csv", "LIESMICH.txt"];
            foreach (var name in expectedEntries)
                Assert.NotNull(zip.GetEntry(name));

            // export.json: Zähler + volle Treue (Genotyp, Import-Herkunft)
            using var json = JsonDocument.Parse(ReadEntry(zip, "export.json", out _));
            var root = json.RootElement;
            Assert.Equal(1, root.GetProperty("gerbils").GetArrayLength());
            Assert.Equal(1, root.GetProperty("litters").GetArrayLength());
            Assert.Equal(1, root.GetProperty("contacts").GetArrayLength());
            Assert.True(root.GetProperty("colorVarieties").GetArrayLength() >= 60); // HasData-Seed (61 nach GEN-3f)
            var g = root.GetProperty("gerbils")[0];
            Assert.Equal("Aa CC Dd EE GG Pp Spsp rere", g.GetProperty("genotype").GetString());
            Assert.Equal("Stammbaum von Akio Kids.xlsx", g.GetProperty("importSource").GetString());
            Assert.Equal(gerbilId.ToString(), g.GetProperty("id").GetString());

            // tiere.csv: BOM, deutsche Kopfzeile, aufgelöste Namen + deutsches Datum
            var tiere = ReadEntry(zip, "tiere.csv", out var hadBom);
            Assert.True(hadBom, "tiere.csv braucht ein UTF-8-BOM für Excel");
            var lines = tiere.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
            Assert.StartsWith("Name;Geschlecht;Status;Geburtsdatum", lines[0]);
            var row = lines[1];
            Assert.Contains("Krümel", row);
            Assert.Contains("Weiblich", row);
            Assert.Contains("Aktiv", row);
            Assert.Contains("12.03.2025", row);
            Assert.Contains("Großbecken", row);
            Assert.Contains("Wurf K", row);

            // kontakte.csv: Escaping im Ernstfall (Semikolon + Anführungszeichen im Namen)
            var kontakte = ReadEntry(zip, "kontakte.csv", out _);
            Assert.Contains("\"Zoohandlung; \"\"Meier\"\"\"", kontakte);

            // LIESMICH erklärt den Foto-Speicherort
            Assert.Contains("photo-storage", ReadEntry(zip, "LIESMICH.txt", out _));
        }

        private static string ReadEntry(ZipArchive zip, string name, out bool hadBom)
        {
            using var stream = zip.GetEntry(name)!.Open();
            using var ms = new MemoryStream();
            stream.CopyTo(ms);
            var bytes = ms.ToArray();
            hadBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
            return Encoding.UTF8.GetString(bytes, hadBom ? 3 : 0, bytes.Length - (hadBom ? 3 : 0));
        }
    }
}
