using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using GerbilManagerWebAPI.Import;
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace GerbilManager.Tests;
///
/// ABGABE-STATUS (Reservierungs-/Abgabe-Status): the pre-handover pipeline
/// verfügbar → reserviert → abgegeben.
/// - Full CRUD round-trip via /reservations.
/// - GET ?gerbilId= filters to one animal.
/// - Invalid status falls back to "verfuegbar".
/// - CRITICAL: rows survive the import re-ingest wipe (loose, FK-free GerbilId/ReservedForContactId).
///
public class SaleReservationEndpointTests : IClassFixture
{
private readonly ApiFactory _factory;
public SaleReservationEndpointTests(ApiFactory factory) => _factory = factory;
[Fact]
public async Task Reservation_crud_lifecycle_create_reserve_handover_delete()
{
var client = _factory.CreateClient();
var gerbilId = Guid.NewGuid();
var contactId = Guid.NewGuid();
// Create -> defaults to verfuegbar.
var create = await client.PostAsJsonAsync("/reservations", new
{
gerbilId,
gerbilName = "Pippa",
});
Assert.Equal(HttpStatusCode.Created, create.StatusCode);
var created = JsonDocument.Parse(await create.Content.ReadAsStringAsync()).RootElement;
var id = created.GetProperty("id").GetString();
Assert.False(string.IsNullOrEmpty(id));
Assert.Equal("verfuegbar", created.GetProperty("status").GetString());
Assert.Equal(gerbilId.ToString(), created.GetProperty("gerbilId").GetString());
Assert.Equal("Pippa", created.GetProperty("gerbilName").GetString());
// List contains it.
var listed = JsonDocument.Parse(await client.GetStringAsync("/reservations")).RootElement;
Assert.Contains(listed.EnumerateArray(), r => r.GetProperty("id").GetString() == id);
// Filter by gerbilId returns exactly this row.
var byGerbil = JsonDocument.Parse(await client.GetStringAsync($"/reservations?gerbilId={gerbilId}")).RootElement;
Assert.All(byGerbil.EnumerateArray(), r => Assert.Equal(gerbilId.ToString(), r.GetProperty("gerbilId").GetString()));
Assert.Contains(byGerbil.EnumerateArray(), r => r.GetProperty("id").GetString() == id);
// PUT -> reserve for a contact with an appointment + price.
var reserve = await client.PutAsJsonAsync($"/reservations/{id}", new
{
status = "reserviert",
reservedForContactId = contactId,
contactName = "Familie Huber",
appointmentDate = "2026-07-01T10:00:00Z",
price = 25.50m,
note = "Käfig wird mitgebracht.",
});
Assert.Equal(HttpStatusCode.OK, reserve.StatusCode);
var reserved = JsonDocument.Parse(await reserve.Content.ReadAsStringAsync()).RootElement;
Assert.Equal("reserviert", reserved.GetProperty("status").GetString());
Assert.Equal(contactId.ToString(), reserved.GetProperty("reservedForContactId").GetString());
Assert.Equal("Familie Huber", reserved.GetProperty("contactName").GetString());
Assert.Equal(25.50m, reserved.GetProperty("price").GetDecimal());
// PUT -> hand over.
var handover = await client.PutAsJsonAsync($"/reservations/{id}", new
{
status = "abgegeben",
reservedForContactId = contactId,
handedOverDate = "2026-07-01T10:30:00Z",
});
Assert.Equal(HttpStatusCode.OK, handover.StatusCode);
var handed = JsonDocument.Parse(await handover.Content.ReadAsStringAsync()).RootElement;
Assert.Equal("abgegeben", handed.GetProperty("status").GetString());
Assert.NotEqual(JsonValueKind.Null, handed.GetProperty("handedOverDate").ValueKind);
// Delete -> 204, then 404.
Assert.Equal(HttpStatusCode.NoContent, (await client.DeleteAsync($"/reservations/{id}")).StatusCode);
Assert.Equal(HttpStatusCode.NotFound, (await client.DeleteAsync($"/reservations/{id}")).StatusCode);
Assert.Equal(HttpStatusCode.NotFound, (await client.PutAsJsonAsync($"/reservations/{id}", new { status = "verfuegbar" })).StatusCode);
}
[Fact]
public async Task Post_with_unknown_status_falls_back_to_verfuegbar()
{
var client = _factory.CreateClient();
var create = await client.PostAsJsonAsync("/reservations", new { gerbilId = Guid.NewGuid(), status = "bananen" });
Assert.Equal(HttpStatusCode.Created, create.StatusCode);
var dto = JsonDocument.Parse(await create.Content.ReadAsStringAsync()).RootElement;
Assert.Equal("verfuegbar", dto.GetProperty("status").GetString());
}
[Fact]
public async Task Post_without_gerbilId_is_rejected()
{
var client = _factory.CreateClient();
var create = await client.PostAsJsonAsync("/reservations", new { gerbilId = Guid.Empty, status = "reserviert" });
Assert.Equal(HttpStatusCode.BadRequest, create.StatusCode);
}
[Fact]
public async Task Reservation_survives_ingest_wipe()
{
// Fresh in-memory DB seeded with a resolved import file (mirrors IngestResolvedServiceTests).
var dir = Path.Combine(Path.GetTempPath(), "reservation-ingest-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
try
{
var contactId = Guid.NewGuid();
var fatherId = Guid.NewGuid();
var motherId = Guid.NewGuid();
var litterId = Guid.NewGuid();
var data = new
{
Contacts = new[]
{
new { Id = contactId, Name = "Test Breeder", Email = "t@e.de", Phone = "", Address = "", Notes = (string?)null, IsBreeder = true, IsReceiver = false, NameSuffix = (string?)null, Provenance = (string?)null }
},
Litters = new[]
{
new { Id = litterId, Name = "Wurf A", Date = "2026-01-01", TotalBorn = 5, DeathsWithin8Weeks = 0, FatherId = fatherId, MotherId = motherId, ExpectedGoHomeDate = (string?)null, Notes = "", PairingCode = "PC01", ExternalRef = "ext-litter-1", LitterLetter = "A" }
},
Gerbils = new[]
{
Animal(fatherId, "Papa", "male", contactId),
Animal(motherId, "Mama", "female", contactId),
},
GerbilPhotos = Array.Empty