using System.IO.Compression;
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace GerbilManager.Tests;

/// <summary>
/// FEAT-13 phase B: Endpoint-Round-Trips für /contracts + /settings/breeder-profile
/// gegen den In-Memory-Host (ApiFactory). Deckt den Abgabe-Abschluss ab:
/// Vertrag erzeugen -> Tiere stehen auf Abgegeben -> .docx ist herunterladbar.
/// </summary>
public class ContractEndpointTests : IClassFixture<ApiFactory>
{
    private static readonly JsonSerializerOptions Json = CreateJsonOptions();

    private static JsonSerializerOptions CreateJsonOptions()
    {
        var o = new JsonSerializerOptions(JsonSerializerDefaults.Web);
        o.Converters.Add(new JsonStringEnumConverter());
        return o;
    }

    private readonly ApiFactory _factory;

    public ContractEndpointTests(ApiFactory factory) => _factory = factory;

    private sealed record IdDto(Guid Id);

    private sealed record GerbilView(Guid Id, string Status, Guid? ReceiverContactId, DateOnly? GoHomeDate);

    private sealed record ContractView(
        Guid Id, Guid ContactId, decimal Price, DateOnly HandoverDate, DateOnly ContractDate,
        string FileName, List<Guid> GerbilIds, string Url);

    private sealed record Paged<T>(List<T> Items, int TotalCount, int Page, int PageSize);

    private async Task<Guid> CreateContactAsync(HttpClient client, string name = "Herr Max Beispiel")
    {
        var response = await client.PostAsJsonAsync("/contacts", new
        {
            name,
            email = "max@example.com",
            phone = "0987 654321",
            address = "Beispielallee 7, 54321 Beispielstadt",
        }, Json);
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        return (await response.Content.ReadFromJsonAsync<IdDto>(Json))!.Id;
    }

    private async Task<Guid> CreateGerbilAsync(HttpClient client, string name)
    {
        var response = await client.PostAsJsonAsync("/gerbils", new
        {
            name,
            gender = "female",
            dateOfBirth = "2025-03-09",
        }, Json);
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        return (await response.Content.ReadFromJsonAsync<IdDto>(Json))!.Id;
    }

    [Fact]
    public async Task ZuchtprofilRoundTrip_PutDannGet()
    {
        var client = _factory.CreateClient();

        var put = await client.PutAsJsonAsync("/settings/breeder-profile", 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",
        }, Json);
        Assert.Equal(HttpStatusCode.NoContent, put.StatusCode);

        var profile = await client.GetFromJsonAsync<Dictionary<string, string>>("/settings/breeder-profile", Json);
        Assert.Equal("Zucht Testhausen", profile!["zuchtName"]);
        Assert.Equal("Testhausen", profile["city"]);
    }

    [Fact]
    public async Task VertragErzeugen_SchließtDieAbgabeAb()
    {
        var client = _factory.CreateClient();

        // Zuchtprofil füllen, damit der Verkäufer-Block im Dokument landet.
        await client.PutAsJsonAsync("/settings/breeder-profile", 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",
        }, Json);

        var contactId = await CreateContactAsync(client);
        var krümel = await CreateGerbilAsync(client, "Krümel");
        var luna = await CreateGerbilAsync(client, "Luna");

        // POST /contracts — der Abschluss.
        var post = await client.PostAsJsonAsync("/contracts", new
        {
            contactId,
            gerbilIds = new[] { krümel, luna },
            price = 72.0m,
            handoverDate = "2026-06-05",
        }, Json);
        Assert.Equal(HttpStatusCode.Created, post.StatusCode);
        var contract = (await post.Content.ReadFromJsonAsync<ContractView>(Json))!;

        Assert.Equal(contactId, contract.ContactId);
        Assert.Equal(2, contract.GerbilIds.Count);
        Assert.Equal(new DateOnly(2026, 6, 5), contract.ContractDate); // Standard = Übergabedatum
        Assert.Equal($"/contracts/{contract.Id}/file", contract.Url);

        // Tiere stehen jetzt auf Abgegeben — mit Abnehmer und Abgabedatum.
        foreach (var id in new[] { krümel, luna })
        {
            var gerbil = await client.GetFromJsonAsync<GerbilView>($"/gerbils/{id}", Json);
            Assert.Equal("GivenAway", gerbil!.Status);
            Assert.Equal(contactId, gerbil.ReceiverContactId);
            Assert.Equal(new DateOnly(2026, 6, 5), gerbil.GoHomeDate);
        }

        // Download: echtes .docx mit beiden Tieren, Käufer, de-DE-Preis.
        var file = await client.GetAsync(contract.Url);
        Assert.Equal(HttpStatusCode.OK, file.StatusCode);
        Assert.Equal(
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            file.Content.Headers.ContentType!.MediaType);
        Assert.Contains("Abgabevertrag_2026-06-05_Herr-Max-Beispiel.docx",
            file.Content.Headers.ContentDisposition!.FileNameStar ?? file.Content.Headers.ContentDisposition.FileName);

        var text = DocumentText(await file.Content.ReadAsByteArrayAsync());
        Assert.Contains("Krümel", text);
        Assert.Contains("Luna", text);
        Assert.Contains("Herr Max Beispiel", text);
        Assert.Contains("Kaufpreis von 72,00 €", text);
        Assert.Contains("Testhausen, den 05.06.2026", text);
        Assert.DoesNotContain("{{", text);

        // Liste: nach Abnehmer filterbar (Gridify).
        var list = await client.GetFromJsonAsync<Paged<ContractView>>(
            $"/contracts?filter=contactId=={contactId}", Json);
        Assert.Equal(1, list!.TotalCount);
        Assert.Equal(contract.Id, list.Items[0].Id);

        // DELETE räumt Zeile und Datei ab; Tier-Status bleibt unangetastet.
        var delete = await client.DeleteAsync($"/contracts/{contract.Id}");
        Assert.Equal(HttpStatusCode.NoContent, delete.StatusCode);
        Assert.Equal(HttpStatusCode.NotFound, (await client.GetAsync(contract.Url)).StatusCode);
        var still = await client.GetFromJsonAsync<GerbilView>($"/gerbils/{krümel}", Json);
        Assert.Equal("GivenAway", still!.Status);
    }

    [Fact]
    public async Task VertragOhneTiereOderMitUnbekanntemTier_Validierungsfehler()
    {
        var client = _factory.CreateClient();
        var contactId = await CreateContactAsync(client, "Frau Lisa Test");

        var empty = await client.PostAsJsonAsync("/contracts", new
        {
            contactId,
            gerbilIds = Array.Empty<Guid>(),
            price = 10m,
            handoverDate = "2026-06-05",
        }, Json);
        Assert.Equal(HttpStatusCode.BadRequest, empty.StatusCode);

        var unknown = await client.PostAsJsonAsync("/contracts", new
        {
            contactId,
            gerbilIds = new[] { Guid.NewGuid() },
            price = 10m,
            handoverDate = "2026-06-05",
        }, Json);
        Assert.Equal(HttpStatusCode.BadRequest, unknown.StatusCode);
    }

    /// <summary>Sichtbarer Text aus word/document.xml (Tags entfernt).</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(), "<[^>]+>", "");
    }
}
