using GerbilManagerWebAPI.Import;
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.Text.Json;

namespace GerbilManager.Tests
{
    public class IngestResolvedServiceTests : IDisposable
    {
        private readonly string _dir;
        private readonly string _resolvedJsonPath;
        private Guid ContactId { get; set; }
        private Guid FatherId { get; set; }
        private Guid ContractId { get; set; }
        private Guid UnknownGerbilId { get; set; }

        public IngestResolvedServiceTests()
        {
            _dir = Path.Combine(Path.GetTempPath(), "ingest-resolved-" + Guid.NewGuid().ToString("N"));
            Directory.CreateDirectory(_dir);
            _resolvedJsonPath = Path.Combine(_dir, "resolved_import.json");

            var contactId = Guid.NewGuid();
            var fatherId = Guid.NewGuid();
            var motherId = Guid.NewGuid();
            var litterId = Guid.NewGuid();
            var photoId = Guid.NewGuid();
            ContactId = contactId;
            FatherId = fatherId;
            ContractId = Guid.NewGuid();
            UnknownGerbilId = Guid.NewGuid();

            var data = new
            {
                Contacts = new[]
                {
                    new
                    {
                        Id = contactId,
                        Name = "Test Breeder",
                        ZuchtName = "Test Zucht",
                        City = "Test City",
                        Email = "test@example.com",
                        Homepage = "",
                        Phone = "",
                        Address = "",
                        Provenance = (string?)"{\"sourceFiles\":[\"Stammbaum\"],\"mergedRecordCount\":1,\"notes\":[\"als Züchter erkannt\"]}"
                    }
                },
                Litters = new[]
                {
                    new
                    {
                        Id = litterId,
                        Name = "Wurf A",
                        Date = "2026-01-01",
                        TotalBorn = 5,
                        DeathsWithin8Weeks = 0,
                        FatherId = fatherId,
                        MotherId = motherId,
                        ExpectedGoHomeDate = (string?)null,
                        Notes = "Test litter notes",
                        PairingCode = "PC01",
                        ExternalRef = "ext-litter-1",
                        LitterLetter = "A",
                        Provenance = (string?)"{\"sourceFiles\":[\"Wurfchronik-Detail.docx\"],\"mergedRecordCount\":1,\"fromWurfchronik\":true,\"notes\":[\"aus Wurfchronik\"]}"
                    }
                },
                Gerbils = new[]
                {
                    new
                    {
                        Id = fatherId,
                        Name = "Papa",
                        Gender = "male",
                        Status = "Breeding",
                        LitterId = (Guid?)null,
                        OriginContactId = contactId,
                        ReceiverContactId = (Guid?)null,
                        EnclosureId = (Guid?)null,
                        ColorVarietyId = new Guid("00000000-0000-0000-0000-000000000006"), // Schwarz
                        DateOfBirth = "2025-01-01",
                        DateOfDeath = (string?)null,
                        CauseOfDeath = (string?)null,
                        GoHomeDate = (string?)null,
                        Genotype = "aa CC DD EE GG PP spsp rere",
                        Notes = "",
                        ImportSource = "docx-export",
                        ExternalRef = "ext-papa",
                        RawImportData = "{}",
                        OriginBreeder = "Test Zucht",
                        NameSearch = "papa",
                        CharacterTraits = new string[] {},
                        CharacterNote = (string?)null,
                        IsDeaf = false,
                        IsResident = true,
                        Provenance = (string?)"{\"sourceFiles\":[\"Stammbaum von Papa.xlsx\",\"Wurfchronik-Detail.docx\"],\"mergedRecordCount\":2,\"fromWurfchronik\":true,\"notes\":[\"aus 2 Datensätzen zusammengeführt\"],\"history\":[\"In \\u201eStammbaum von Papa.xlsx\\u201c gefunden.\",\"Genotyp aus \\u201eWurfchronik-Detail.docx\\u201c.\"]}"
                    },
                    new
                    {
                        Id = motherId,
                        Name = "Mama",
                        Gender = "female",
                        Status = "Breeding",
                        LitterId = (Guid?)null,
                        OriginContactId = contactId,
                        ReceiverContactId = (Guid?)null,
                        EnclosureId = (Guid?)null,
                        ColorVarietyId = new Guid("00000000-0000-0000-0000-000000000006"), // Schwarz
                        DateOfBirth = "2025-01-01",
                        DateOfDeath = (string?)null,
                        CauseOfDeath = (string?)null,
                        GoHomeDate = (string?)null,
                        Genotype = "aa CC DD EE GG PP spsp rere",
                        Notes = "",
                        ImportSource = "docx-export",
                        ExternalRef = "ext-mama",
                        RawImportData = "{}",
                        OriginBreeder = "Test Zucht",
                        NameSearch = "mama",
                        CharacterTraits = new string[] {},
                        CharacterNote = (string?)null,
                        IsDeaf = false,
                        IsResident = true,
                        Provenance = (string?)null
                    }
                },
                GerbilPhotos = new[]
                {
                    new
                    {
                        Id = photoId,
                        GerbilId = fatherId,
                        FileName = "photo1.jpg",
                        SortOrder = 0
                    }
                },
                SaleContracts = new[]
                {
                    new
                    {
                        Id = ContractId,
                        ContactId = contactId,
                        Price = 35.0m,
                        HandoverDate = "2023-09-29",
                        ContractDate = "2023-09-29",
                        FileName = "Zucht der kleinen Chaoten _ (Papa) - Test Breeder_.docx",
                        // One known animal + one unknown id (must be skipped defensively).
                        Animals = new[] { fatherId, UnknownGerbilId }
                    }
                }
            };

            File.WriteAllText(_resolvedJsonPath, JsonSerializer.Serialize(data));
        }

        public void Dispose()
        {
            try { Directory.Delete(_dir, recursive: true); } catch { }
        }

        private ApplicationContext NewDb()
        {
            var opts = new DbContextOptionsBuilder<ApplicationContext>()
                .UseInMemoryDatabase("ingest-resolved-" + Guid.NewGuid().ToString("N"))
                .Options;
            var db = new ApplicationContext(opts);
            db.Database.EnsureCreated();
            return db;
        }

        [Fact]
        public async Task IngestResolved_loads_data_and_resolves_all_relations()
        {
            using var db = NewDb();
            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(new Dictionary<string, string?>
                {
                    { "Import:SourcePath", _dir }
                })
                .Build();

            var service = new IngestResolvedService(db, config, null!);
            var result = await service.RunAsync();

            Assert.Contains("Ingestion successful!", result);

            // Verify counts
            Assert.Equal(1, await db.Contacts.CountAsync());
            Assert.Equal(1, await db.Litters.CountAsync());
            Assert.Equal(2, await db.Gerbils.CountAsync());
            Assert.Equal(1, await db.GerbilPhotos.CountAsync());

            // Verify relations
            var litter = await db.Litters.SingleAsync();
            var father = await db.Gerbils.SingleAsync(g => g.Name == "Papa");
            var mother = await db.Gerbils.SingleAsync(g => g.Name == "Mama");
            var photo = await db.GerbilPhotos.SingleAsync();

            Assert.Equal(father.Id, litter.FatherId);
            Assert.Equal(mother.Id, litter.MotherId);
            Assert.Equal(father.Id, photo.GerbilId);
            Assert.Equal(father.OriginContactId, mother.OriginContactId);

            // Provenance round-trips verbatim through ingest (and stays null when absent).
            Assert.NotNull(father.Provenance);
            Assert.Contains("Stammbaum von Papa.xlsx", father.Provenance);
            Assert.Contains("mergedRecordCount", father.Provenance);
            // The chronological, file-attributed history survives ingest verbatim
            // (stored as opaque JSON on the text column — no schema for it).
            Assert.Contains("history", father.Provenance);
            Assert.Contains("Genotyp aus", father.Provenance);
            Assert.Null(mother.Provenance);

            // Contact + litter provenance also round-trips through the ingest.
            var contact = await db.Contacts.SingleAsync();
            Assert.NotNull(contact.Provenance);
            Assert.Contains("als Züchter erkannt", contact.Provenance);

            Assert.NotNull(litter.Provenance);
            Assert.Contains("aus Wurfchronik", litter.Provenance);
            Assert.Contains("fromWurfchronik", litter.Provenance);

            // SaleContracts are created from the payload, and animal links to
            // unknown gerbils are skipped defensively (only the known father links).
            Assert.Equal(1, await db.SaleContracts.CountAsync());
            var contract = await db.SaleContracts.Include(c => c.Animals).SingleAsync();
            Assert.Equal(ContractId, contract.Id);
            Assert.Equal(ContactId, contract.ContactId);
            Assert.Equal(35.0m, contract.Price);
            Assert.Equal(new DateOnly(2023, 9, 29), contract.HandoverDate);
            var link = Assert.Single(contract.Animals);
            Assert.Equal(FatherId, link.GerbilId);
        }

        [Fact]
        public async Task IngestResolved_recreates_sale_contracts_on_every_ingest()
        {
            using var db = NewDb();
            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(new Dictionary<string, string?>
                {
                    { "Import:SourcePath", _dir }
                })
                .Build();

            var service = new IngestResolvedService(db, config, null!);
            await service.RunAsync();
            Assert.Equal(1, await db.SaleContracts.CountAsync());

            // Re-ingest wipes then recreates from the (deterministic) payload —
            // the contract survives by being part of the payload, no duplicate PK.
            await service.RunAsync();
            Assert.Equal(1, await db.SaleContracts.CountAsync());
            var contract = await db.SaleContracts.Include(c => c.Animals).SingleAsync();
            Assert.Equal(ContractId, contract.Id);
            Assert.Single(contract.Animals);
        }
    }
}
