using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public ApplicationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Gerbil> Gerbils => Set<Gerbil>();
    public DbSet<Litter> Litters => Set<Litter>();
    public DbSet<Contact> Contacts => Set<Contact>();
    public DbSet<Enclosure> Enclosures => Set<Enclosure>();
    public DbSet<ColorVariety> ColorVarieties => Set<ColorVariety>();
    public DbSet<GerbilPhoto> GerbilPhotos => Set<GerbilPhoto>();
    public DbSet<HealthRecord> HealthRecords => Set<HealthRecord>();
    public DbSet<WeightRecord> WeightRecords => Set<WeightRecord>();
    public DbSet<SaleContract> SaleContracts => Set<SaleContract>();
    public DbSet<BreederSettings> BreederSettings => Set<BreederSettings>();
    public DbSet<Site> Sites => Set<Site>();
    public DbSet<Page> Pages => Set<Page>();
    public DbSet<Block> Blocks => Set<Block>();
    public DbSet<Media> Media => Set<Media>();
    public DbSet<Request> Requests => Set<Request>();
    public DbSet<MailSettings> MailSettings => Set<MailSettings>();

    // Keep Gerbil.NameSearch in sync on every save (separator-insensitive search key),
    // so it can never drift from Name regardless of which code path mutates the entity.
    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        SyncNameSearch();
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        SyncNameSearch();
        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

    private void SyncNameSearch()
    {
        foreach (var entry in ChangeTracker.Entries<Gerbil>())
        {
            if (entry.State is EntityState.Added or EntityState.Modified)
                entry.Entity.NameSearch = GerbilSearch.Normalize(entry.Entity.Name);
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // DB-4: ICU German collation on user-visible / searched text columns.
        // "de-x-icu" gives correct German sort order (ä between a and b) and locale-aware
        // case-folding (lower('Ä')='ä'). Applied only on Npgsql (Postgres); SQLite does not
        // support custom collation names and would fail EnsureCreated on the test host.
        const string DeIcu = "de-x-icu";
        bool isNpgsql = Database.ProviderName?.Contains("Npgsql", StringComparison.OrdinalIgnoreCase) ?? false;

        modelBuilder.Entity<Gerbil>(e =>
        {
            // Enums persisted as their string names (readable, Gridify-friendly).
            e.Property(g => g.Gender).HasConversion<string>();
            e.Property(g => g.Status).HasConversion<string>();

            // Residency defaults to true (own stock unless explicitly marked external).
            e.Property(g => g.IsResident).HasDefaultValue(true);

            // DB-4: German collation on searched/sorted name fields (Npgsql-only).
            if (isNpgsql)
            {
                e.Property(g => g.Name).UseCollation(DeIcu);
                e.Property(g => g.NameSearch).UseCollation(DeIcu);
                e.Property(g => g.OriginBreeder).UseCollation(DeIcu);
            }

            // FEAT-14: character traits stored as a JSON text column (works on both
            // Npgsql and the SQLite test host; opaque labels, no backend vocabulary).
            var traitsConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<List<string>, string>(
                v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions?)null),
                v => string.IsNullOrEmpty(v)
                    ? new List<string>()
                    : System.Text.Json.JsonSerializer.Deserialize<List<string>>(v, (System.Text.Json.JsonSerializerOptions?)null) ?? new List<string>());
            var traitsComparer = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer<List<string>>(
                (a, b) => (a ?? new List<string>()).SequenceEqual(b ?? new List<string>()),
                v => v == null ? 0 : v.Aggregate(0, (h, s) => HashCode.Combine(h, s.GetHashCode())),
                v => v.ToList());
            e.Property(g => g.CharacterTraits).HasConversion(traitsConverter, traitsComparer);

            e.HasOne(g => g.Litter).WithMany()
                .HasForeignKey(g => g.LitterId).OnDelete(DeleteBehavior.SetNull);
            e.HasOne(g => g.OriginContact).WithMany()
                .HasForeignKey(g => g.OriginContactId).OnDelete(DeleteBehavior.Restrict);
            e.HasOne(g => g.ReceiverContact).WithMany()
                .HasForeignKey(g => g.ReceiverContactId).OnDelete(DeleteBehavior.Restrict);
            e.HasOne(g => g.Enclosure).WithMany(en => en.Gerbils)
                .HasForeignKey(g => g.EnclosureId).OnDelete(DeleteBehavior.SetNull);
            e.HasOne(g => g.ColorVariety).WithMany()
                .HasForeignKey(g => g.ColorVarietyId).OnDelete(DeleteBehavior.SetNull);

            // DB-1: ExternalRef is the import idempotency key — enforce uniqueness at the DB level.
            // Filtered (nulls allowed: manually-entered animals have no ExternalRef).
            e.HasIndex(g => g.ExternalRef)
                .IsUnique()
                .HasFilter("\"ExternalRef\" IS NOT NULL");
        });

        modelBuilder.Entity<Litter>(e =>
        {
            e.HasOne(l => l.Father).WithMany()
                .HasForeignKey(l => l.FatherId).OnDelete(DeleteBehavior.Restrict);
            e.HasOne(l => l.Mother).WithMany()
                .HasForeignKey(l => l.MotherId).OnDelete(DeleteBehavior.Restrict);

            // DB-5: ExternalRef = source litter id from extract.py.
            // Unique (filtered, nulls allowed for manually-entered litters).
            e.HasIndex(l => l.ExternalRef)
                .IsUnique()
                .HasFilter("\"ExternalRef\" IS NOT NULL");
        });

        modelBuilder.Entity<HealthRecord>(e =>
        {
            e.Property(h => h.Type).HasConversion<string>();
            e.HasOne<Gerbil>().WithMany()
                .HasForeignKey(h => h.GerbilId).OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<WeightRecord>(e =>
            e.HasOne<Gerbil>().WithMany()
                .HasForeignKey(w => w.GerbilId).OnDelete(DeleteBehavior.Cascade));

        modelBuilder.Entity<GerbilPhoto>(e =>
            e.HasOne<Gerbil>().WithMany()
                .HasForeignKey(p => p.GerbilId).OnDelete(DeleteBehavior.Cascade));

        // FEAT-13: Abgabeverträge + Zuchtprofil.
        modelBuilder.Entity<SaleContract>(e =>
        {
            e.Property(c => c.Price).HasPrecision(10, 2);
            // Restrict: ein Kontakt mit Verträgen ist ein Dokumentenbestand,
            // kein versehentlich löschbarer Datensatz.
            e.HasOne(c => c.Contact).WithMany()
                .HasForeignKey(c => c.ContactId).OnDelete(DeleteBehavior.Restrict);
        });

        modelBuilder.Entity<SaleContractAnimal>(e =>
        {
            e.HasKey(a => new { a.SaleContractId, a.GerbilId });
            e.HasOne<SaleContract>().WithMany(c => c.Animals)
                .HasForeignKey(a => a.SaleContractId).OnDelete(DeleteBehavior.Cascade);
            // Cascade: wird ein Tier gelöscht, verschwindet nur die Verknüpfung —
            // der Vertrag (und seine .docx als Beleg) bleibt bestehen.
            e.HasOne(a => a.Gerbil).WithMany()
                .HasForeignKey(a => a.GerbilId).OnDelete(DeleteBehavior.Cascade);
        });

        // Zuchtprofil: genau eine (leere) Zeile mit fixer Id.
        modelBuilder.Entity<BreederSettings>()
            .HasData(new BreederSettings { Id = GerbilManagerWebAPI.Models.BreederSettings.SingletonId });

        // WEB epic: CMS content model.
        modelBuilder.Entity<Site>(e =>
        {
            var navConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<List<Guid>, string>(
                v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions?)null),
                v => string.IsNullOrEmpty(v)
                    ? new List<Guid>()
                    : System.Text.Json.JsonSerializer.Deserialize<List<Guid>>(v, (System.Text.Json.JsonSerializerOptions?)null) ?? new List<Guid>());
            var navComparer = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer<List<Guid>>(
                (a, b) => (a ?? new List<Guid>()).SequenceEqual(b ?? new List<Guid>()),
                v => v == null ? 0 : v.Aggregate(0, (h, x) => HashCode.Combine(h, x.GetHashCode())),
                v => v.ToList());
            e.Property(s => s.NavOrder).HasConversion(navConverter, navComparer);
        });

        modelBuilder.Entity<Page>(e =>
        {
            e.HasIndex(p => p.Slug).IsUnique();
            e.Property(p => p.Status).HasConversion<string>();
            e.HasMany(p => p.Blocks).WithOne()
                .HasForeignKey(b => b.PageId).OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<Block>(e => e.Property(b => b.Type).HasConversion<string>());

        SeedCms(modelBuilder);

        // INBOX epic: Gmail request inbox.
        modelBuilder.Entity<Request>(e =>
        {
            e.Property(r => r.Status).HasConversion<string>();
            e.HasIndex(r => r.GmailMessageId).IsUnique();
            e.HasOne(r => r.AssignedContact).WithMany()
                .HasForeignKey(r => r.AssignedContactId).OnDelete(DeleteBehavior.Restrict);
        });
        // exactly one MailSettings row, fixed id.
        modelBuilder.Entity<MailSettings>()
            .HasData(new MailSettings { Id = GerbilManagerWebAPI.Models.MailSettings.SingletonId });

        // DB-4: German collation on remaining searched/sorted text columns (Npgsql-only).
        if (isNpgsql)
        {
            modelBuilder.Entity<ColorVariety>()
                .Property(v => v.Name).UseCollation(DeIcu);
            modelBuilder.Entity<Contact>()
                .Property(c => c.Name).UseCollation(DeIcu);
        }

        SeedColorVarieties(modelBuilder);
    }

    /// <summary>
    /// Seed the public website's CMS: one Site + the 6 Jimdo-mirror pages, each with a
    /// placeholder Heading; the abgabetiere page also carries an auto AbgabetiereList block
    /// (resolved from live ForSale animals at snapshot time). Hand-editable afterwards.
    /// </summary>
    private static void SeedCms(ModelBuilder modelBuilder)
    {
        Guid Pid(int n) => new($"51720001-0000-0000-0000-{n:D12}");
        Guid Bid(int n) => new($"51720002-0000-0000-0000-{n:D12}");

        (int n, string Slug, string Title)[] pages =
        {
            (1, "start", "Startseite"),
            (2, "ueber-die-zucht", "Über die Zucht"),
            (3, "abgabetiere", "Abgabetiere"),
            (4, "abgabebedingungen", "Abgabebedingungen"),
            (5, "farben-genetik", "Farben & Genetik"),
            (6, "kontakt", "Kontakt"),
        };

        // WEB-6a: footer-only legal pages (not in main nav)
        (int n, string Slug, string Title, string BodyMd)[] legalPages =
        {
            (7, "impressum", "Impressum",
                "**Angaben gemäß § 5 TMG**\\n\\nSeitenbetreiber: [Name und vollständige Adresse eintragen]\\n\\nE-Mail: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*"),
            (8, "datenschutz", "Datenschutz",
                "**Datenschutzerklärung**\\n\\nDiese Webseite dient der Vorstellung unserer Rennmauszucht. Es werden keine personenbezogenen Daten gespeichert oder weitergegeben.\\n\\nBei datenschutzbezogenen Fragen: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*"),
        };

        var pageRows = new List<Page>();
        var blockRows = new List<Block>();
        foreach (var p in pages)
        {
            pageRows.Add(new Page { Id = Pid(p.n), Slug = p.Slug, Title = p.Title, Status = PageStatus.Published });
            // placeholder Heading per page
            blockRows.Add(new Block
            {
                Id = Bid(p.n), PageId = Pid(p.n), Order = 0, Type = BlockType.Heading,
                Data = $"{{\"text\":\"{p.Title}\",\"level\":1}}",
            });
        }
        // abgabetiere (n=3): auto AbgabetiereList as the second block
        blockRows.Add(new Block
        {
            Id = Bid(10), PageId = Pid(3), Order = 1, Type = BlockType.AbgabetiereList,
            Data = "{\"mode\":\"auto\",\"intro\":\"\"}",
        });
        foreach (var lp in legalPages)
        {
            pageRows.Add(new Page { Id = Pid(lp.n), Slug = lp.Slug, Title = lp.Title, Status = PageStatus.Published });
            blockRows.Add(new Block { Id = Bid(lp.n), PageId = Pid(lp.n), Order = 0, Type = BlockType.Heading, Data = $"{{\"text\":\"{lp.Title}\",\"level\":1}}" });
            blockRows.Add(new Block { Id = Bid(lp.n * 10), PageId = Pid(lp.n), Order = 1, Type = BlockType.RichText, Data = $"{{\"markdown\":\"{lp.BodyMd}\"}}" });
        }

        modelBuilder.Entity<Page>().HasData(pageRows);
        modelBuilder.Entity<Block>().HasData(blockRows);

        modelBuilder.Entity<Site>().HasData(new Site
        {
            Id = Site.SingletonId,
            DefaultLocale = "de",
            NavOrder = pages.Select(p => Pid(p.n)).ToList(),
        });
    }

    /// <summary>
    /// Seed the colour-variety catalog. Source of truth = Kevin's GEN-2 generated list
    /// (gerbil-manager-web/src/genetics/colorVarietySeed.generated.json): 73 varieties
    /// (the 18 frozen-contract names first, then the baseportal.de extension), in stable
    /// SortOrder. Names are UI filter keys — spelling changes route through god.
    /// </summary>
    private static void SeedColorVarieties(ModelBuilder modelBuilder)
    {
        // GEN-3g (Kevin): 5 CP-*-Hell variants appended at the end (IDs 62-66).
        // Existing 61 entries are UNCHANGED (same ID->Name binding preserved).
        // SortOrder for new entries is appended; Kevin's frontend catalog handles
        // the interleaved display order via its own sortOrder values.
        (string Name, string Genotype)[] catalog =
        {
            ("Pink Eyed White (PEW)", "AA chch DD EE GG pp spsp rere"),
            ("Hermelin", "aa chch DD EE GG PP spsp rere"),
            ("Himalaya", "AA chch DD EE GG PP spsp rere"),
            ("Zobel", "aa cchmcchm DD EE gg PP spsp rere"),
            ("Rotaugenschimmel", "AA CC DD efef GG pp spsp rere"),
            ("Agouti", "AA CC DD EE GG PP spsp rere"),
            ("Schwarz", "aa CC DD EE GG PP spsp rere"),
            ("Silberagouti", "AA CC DD EE gg PP spsp rere"),
            ("Anthrazit", "aa CC DD EE gg PP spsp rere"),
            ("Algierfuchs", "AA CC DD ee GG PP spsp rere"),
            ("Blau", "aa CC dd EE GG PP spsp rere"),
            ("Gold", "AA CC DD EE GG pp spsp rere"),
            ("Platin", "aa CC DD EE GG pp spsp rere"),
            ("Goldfuchs", "AA CC DD ee GG pp spsp rere"),
            ("Rotfuchs", "aa CC DD ee GG pp spsp rere"),
            ("dd Gold", "AA CC dd EE GG pp spsp rere"),
            ("dd Platin", "aa CC dd EE GG pp spsp rere"),
            ("Altweiss (REW)", "aa CC DD EE gg pp spsp rere"),
            ("Apricot (Blassfuchs)", "AA CC DD ee gg pp spsp rere"),
            ("Blaufuchs", "aa CC DD ee gg PP spsp rere"),
            ("C-Separator", "aa CC DD ee gg pp spsp rere"),
            ("Elfenbein", "AA CC DD EE gg pp spsp rere"),
            ("Kohlfuchs", "aa CC DD ee GG PP spsp rere"),
            ("Polarfuchs", "AA CC DD ee gg PP spsp rere"),
            ("Saphir", "aa CC DD EE GG pp spsp rere"),
            ("Orangeschimmel", "AA CC DD efef GG PP spsp rere"),
            ("Topas", "AA CC DD EE GG pp spsp rere"),
            ("Platin-Hell", "aa CC DD EE GG pp spsp rere"),
            ("Agouti dd", "AA CC dd EE GG PP spsp rere"),
            ("Silberagouti dd", "AA CC dd EE gg PP spsp rere"),
            ("Kohlfuchs dd", "aa CC dd ee GG PP spsp rere"),
            ("Anthrazit dd", "aa CC dd EE gg PP spsp rere"),
            ("Silberschimmel", "AA CC DD efef gg PP spsp rere"),
            ("Polarfuchsschimmel", "AA CC DD efef gg PP spsp rere"),
            ("Algierfuchsschimmel", "AA CC DD efef GG PP spsp rere"),
            ("Kohlfuchsschimmel", "aa CC DD efef GG PP spsp rere"),
            ("Blaufuchsschimmel", "aa CC DD efef gg PP spsp rere"),
            ("Kohlfuchs, hell", "aa CC DD ee GG PP spsp rere"),
            ("Goldfuchs, hell", "AA CC DD ee GG pp spsp rere"),
            ("Goldfuchsschimmel", "AA CC DD efef GG pp spsp rere"),
            ("Gold-Hell", "AA CC DD EE GG pp spsp rere"),
            ("Blaufuchs, hell", "aa CC DD ee gg PP spsp rere"),
            ("Rotfuchsschimmel", "aa CC DD efef GG pp spsp rere"),
            ("Polarfuchs, hell", "AA CC DD ee gg PP spsp rere"),
            ("Kohlfuchsschimmel, hell", "aa CC DD efef GG PP spsp rere"),
            ("Rotfuchs, hell", "aa CC DD ee GG pp spsp rere"),
            ("Kohlfuchs-Hell", "aa CC DD ee GG PP spsp rere"),
            ("Algierfuchs, hell", "AA CC DD ee GG PP spsp rere"),
            ("Topas dd", "AA CC dd EE GG pp spsp rere"),
            ("Blaufuchs dd", "aa CC dd ee gg pp spsp rere"),
            ("Marder", "aa cchmcchm DD EE GG PP spsp rere"),
            ("Siam", "aa cchmch DD EE GG PP spsp rere"),
            ("Zobel-Hell", "aa cchmch DD EE gg PP spsp rere"),
            ("CP-Agouti", "AA cchmcchm DD EE GG PP spsp rere"),
            ("CP-Silberagouti", "AA cchmcchm DD EE gg PP spsp rere"),
            ("CP-Algierfuchs", "AA cchmcchm DD ee GG PP spsp rere"),
            ("CP-Polarfuchs", "AA cchmcchm DD ee gg PP spsp rere"),
            ("CP-Fuchs", "AA cchmcchm dd ee GG PP spsp rere"),
            ("CP-Fuchs-Hell", "AA cchmch dd ee GG PP spsp rere"),
            ("CP-Blaufuchs", "AA cchmcchm dd ee gg PP spsp rere"),
            ("CP-Orangeschimmel", "AA cchmcchm DD efef GG PP spsp rere"),
            // GEN-3g: 5 new CP-*-Hell variants appended (IDs 62-66, no ID->Name drift)
            ("CP-Agouti-Hell",       "AA cchmch DD EE GG PP spsp rere"),
            ("CP-Silberagouti-Hell", "AA cchmch DD EE gg PP spsp rere"),
            ("CP-Algierfuchs-Hell",  "AA cchmch DD ee GG PP spsp rere"),
            ("CP-Polarfuchs-Hell",   "AA cchmch DD ee gg PP spsp rere"),
            ("CP-Orangeschimmel-Hell","AA cchmch DD efef GG PP spsp rere"),
        };

        var rows = new ColorVariety[catalog.Length];
        for (int i = 0; i < catalog.Length; i++)
        {
            rows[i] = new ColorVariety
            {
                // Stable, deterministic GUIDs so the HasData seed is migration-stable.
                Id = new Guid($"00000000-0000-0000-0000-{(i + 1):D12}"),
                Name = catalog[i].Name,
                CanonicalGenotype = catalog[i].Genotype,
                SortOrder = i,
            };
        }
        modelBuilder.Entity<ColorVariety>().HasData(rows);
    }
}
