using GerbilManagerWebAPI.Common;
using GerbilManagerWebAPI.Contracts;
using GerbilManagerWebAPI.Dtos;
using GerbilManagerWebAPI.Models;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;

namespace GerbilManagerWebAPI.Endpoints
{
    /// <summary>
    /// FEAT-13: Abgabeverträge.
    ///   POST   /contracts            -> erzeugt .docx (phase-A-Generator), speichert sie im
    ///                                   Vertrags-Dateiroot, legt die Vertragszeile an und stellt
    ///                                   die Tiere in DERSELBEN Transaktion auf Abgegeben
    ///                                   (ReceiverContactId, GoHomeDate, Status).
    ///   GET    /contracts            -> Gridify-paged (z. B. filter=contactId==…)
    ///   GET    /contracts/{id}       -> Metadaten
    ///   GET    /contracts/{id}/file  -> .docx-Download (deutscher Dateiname)
    ///   DELETE /contracts/{id}       -> entfernt Zeile + Datei (Tier-Status bleibt unberührt)
    /// </summary>
    public static class ContractEndpoints
    {
        private const string DocxContentType =
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document";

        public static IEndpointRouteBuilder MapContractEndpoints(this IEndpointRouteBuilder app)
        {
            var group = app.MapGroup("/contracts").WithTags("Contracts");

            // GET /contracts (Gridify; GerbilIds via Include mitgeladen)
            group.MapGet("/", async ([AsParameters] GridifyParams query, ApplicationContext db) =>
                TypedResults.Ok(await db.SaleContracts.AsNoTracking()
                    .Include(c => c.Animals)
                    .ToPagedResultAsync(query, ToDto)));

            // GET /contracts/{id}
            group.MapGet("/{id:guid}", async Task<Results<Ok<SaleContractDto>, NotFound>> (Guid id, ApplicationContext db) =>
            {
                var c = await db.SaleContracts.AsNoTracking()
                    .Include(x => x.Animals)
                    .FirstOrDefaultAsync(x => x.Id == id);
                return c is null ? TypedResults.NotFound() : TypedResults.Ok(ToDto(c));
            });

            // POST /contracts — der Abgabe-Abschluss.
            group.MapPost("/", async Task<Results<Created<SaleContractDto>, ValidationProblem>> (
                SaleContractInput input, ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var errors = new Dictionary<string, string[]>();

                var gerbilIds = (input.GerbilIds ?? []).Distinct().ToList();
                if (gerbilIds.Count == 0)
                    errors["gerbilIds"] = ["Mindestens ein Tier auswählen."];
                if (input.Price < 0)
                    errors["price"] = ["Der Kaufpreis darf nicht negativ sein."];

                var contact = await db.Contacts.AsNoTracking()
                    .FirstOrDefaultAsync(c => c.Id == input.ContactId);
                if (contact is null)
                    errors["contactId"] = ["Der Abnehmer wurde nicht gefunden."];

                var gerbils = await db.Gerbils
                    .Include(g => g.ColorVariety)
                    .Where(g => gerbilIds.Contains(g.Id))
                    .ToListAsync();
                if (gerbils.Count != gerbilIds.Count)
                    errors["gerbilIds"] = ["Mindestens ein ausgewähltes Tier wurde nicht gefunden."];

                if (errors.Count > 0) return TypedResults.ValidationProblem(errors);

                var settings = await db.BreederSettings.AsNoTracking()
                    .FirstOrDefaultAsync(s => s.Id == BreederSettings.SingletonId)
                    ?? new BreederSettings();

                var contractDate = input.ContractDate ?? input.HandoverDate;
                var data = new ContractData(
                    Seller: ToSeller(settings),
                    Buyer: new ContractBuyer(contact!.Name, contact.Address ?? "", contact.Phone, contact.Email),
                    Animals: gerbils
                        .Select(g => new ContractAnimal(g.Name, GeschlechtText(g.Gender), g.DateOfBirth, g.ColorVariety?.Name))
                        .ToList(),
                    Price: input.Price,
                    HandoverDate: input.HandoverDate,
                    ContractDate: contractDate);

                var bytes = ContractGenerator.Generate(data);

                var fileName = $"{Guid.NewGuid():N}.docx";
                var root = ContractRoot(config, env);
                Directory.CreateDirectory(root);
                await File.WriteAllBytesAsync(Path.Combine(root, fileName), bytes);

                var entity = new SaleContract
                {
                    Id = Guid.NewGuid(),
                    ContactId = contact.Id,
                    Price = input.Price,
                    HandoverDate = input.HandoverDate,
                    ContractDate = contractDate,
                    FileName = fileName,
                    CreatedAt = DateTimeOffset.UtcNow,
                    Animals = gerbilIds.Select(id => new SaleContractAnimal { GerbilId = id }).ToList(),
                };
                db.SaleContracts.Add(entity);

                // Abgabe-Abschluss-Semantik: in derselben SaveChanges-Transaktion.
                foreach (var g in gerbils)
                {
                    g.ReceiverContactId = contact.Id;
                    g.GoHomeDate = input.HandoverDate;
                    g.Status = GerbilStatus.GivenAway;
                }

                try
                {
                    await db.SaveChangesAsync();
                }
                catch
                {
                    // DB fehlgeschlagen -> verwaiste Datei wieder aufräumen.
                    var orphan = Path.Combine(root, fileName);
                    if (File.Exists(orphan)) File.Delete(orphan);
                    throw;
                }

                return TypedResults.Created($"/contracts/{entity.Id}", ToDto(entity));
            });

            // GET /contracts/{id}/file — Download mit sprechendem deutschen Dateinamen.
            group.MapGet("/{id:guid}/file", async Task<Results<PhysicalFileHttpResult, NotFound>> (
                Guid id, ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var c = await db.SaleContracts.AsNoTracking()
                    .Include(x => x.Contact)
                    .FirstOrDefaultAsync(x => x.Id == id);
                if (c is null) return TypedResults.NotFound();

                var path = Path.Combine(ContractRoot(config, env), c.FileName);
                if (!File.Exists(path)) return TypedResults.NotFound();

                var download = $"Abgabevertrag_{c.ContractDate:yyyy-MM-dd}_{Sanitize(c.Contact?.Name)}.docx";
                return TypedResults.PhysicalFile(path, DocxContentType, download);
            });

            // DELETE /contracts/{id} — Zeile + Datei; Tier-Status wird NICHT zurückgedreht
            // (das wäre Magie — Status korrigiert man am Tier selbst).
            group.MapDelete("/{id:guid}", async Task<Results<NoContent, NotFound>> (
                Guid id, ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var c = await db.SaleContracts.FirstOrDefaultAsync(x => x.Id == id);
                if (c is null) return TypedResults.NotFound();

                var path = Path.Combine(ContractRoot(config, env), c.FileName);
                if (File.Exists(path)) File.Delete(path);

                db.SaleContracts.Remove(c); // Joins kaskadieren
                await db.SaveChangesAsync();
                return TypedResults.NoContent();
            });

            return app;
        }

        private static string ContractRoot(IConfiguration config, IWebHostEnvironment env) =>
            config["Contracts:RootPath"] ?? Path.Combine(env.ContentRootPath, "contract-storage");

        private static BreederProfile ToSeller(BreederSettings s) => new()
        {
            ZuchtName = s.ZuchtName,
            Name = s.Name,
            Address = s.Address,
            Phone = s.Phone,
            Email = s.Email,
            Homepage = s.Homepage,
            City = s.City,
        };

        /// <summary>Deutscher Anzeigetext fürs Geschlecht (Vertragsdokument).</summary>
        internal static string GeschlechtText(Gender gender) => gender switch
        {
            Gender.male => "Männlich",
            Gender.female => "Weiblich",
            _ => "Unbekannt",
        };

        /// <summary>Kontaktname → Dateinamens-tauglich (Umlaute bleiben, Trenner -> '-').</summary>
        private static string Sanitize(string? name)
        {
            if (string.IsNullOrWhiteSpace(name)) return "Abnehmer";
            var cleaned = new string(name.Trim()
                .Select(ch => char.IsLetterOrDigit(ch) ? ch : '-')
                .ToArray());
            return cleaned.Trim('-');
        }

        internal static SaleContractDto ToDto(SaleContract c) => new(
            c.Id, c.ContactId, c.Price, c.HandoverDate, c.ContractDate, c.FileName, c.CreatedAt,
            c.Animals.Select(a => a.GerbilId).ToList(),
            $"/contracts/{c.Id}/file");
    }
}
