using GerbilManagerWebAPI.Dtos;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;

namespace GerbilManagerWebAPI.Endpoints
{
    /// <summary>
    /// REFS: Auflöser für getippte Kurz-Verweise (Shortlinks, git-Stil) in Freitexten.
    /// Tokens im Text: <c>tier:&lt;code&gt;</c>, <c>kontakt:&lt;code&gt;</c>,
    /// <c>wurf:&lt;code&gt;</c>, <c>gehege:&lt;code&gt;</c> — <c>&lt;code&gt;</c> ist ein
    /// GUID-Präfix (mind. 6 Hex) oder die volle GUID. Das Frontend rendert daraus
    /// Name + interner Link.
    ///
    ///   POST /refs/resolve  -> [{ type, code, id|null, name|null }]
    ///
    /// DB-AGNOSTISCH (Tests: SQLite in-memory, Prod: Postgres): pro Typ eine schlanke
    /// {Id,Name}-Projektion laden und im Speicher per Präfix matchen — KEIN ::text-Cast
    /// o.ä. Mehrdeutiges Präfix (mehrere Treffer) => id=null (nicht raten).
    /// </summary>
    public static class RefsEndpoints
    {
        /// <summary>Schlanke {Id,Name}-Projektion für den In-Memory-Präfix-Match.</summary>
        private readonly record struct NamedRow(Guid Id, string Name);

        public static IEndpointRouteBuilder MapRefsEndpoints(this IEndpointRouteBuilder app)
        {
            var group = app.MapGroup("/refs").WithTags("Refs");

            group.MapPost("/resolve", async Task<Ok<List<RefResolution>>> (
                ResolveRefsInput input, ApplicationContext db) =>
            {
                var refs = input.Refs ?? new List<RefRequest>();

                // Welche Entitätstypen werden überhaupt angefragt? Nur diese Tabellen laden.
                var wantedTypes = refs
                    .Select(r => NormalizeType(r.Type))
                    .Where(t => t is not null)
                    .Distinct()
                    .ToHashSet();

                // Pro Typ einmalig eine leichte {Id,Name}-Projektion laden (DB-agnostisch:
                // reine Spaltenauswahl, kein ::text-Cast — läuft auf SQLite wie auf Postgres).
                var byType = new Dictionary<string, List<NamedRow>>();
                if (wantedTypes.Contains("tier"))
                    byType["tier"] = await db.Gerbils.AsNoTracking()
                        .Select(x => new NamedRow(x.Id, x.Name)).ToListAsync();
                if (wantedTypes.Contains("kontakt"))
                    byType["kontakt"] = await db.Contacts.AsNoTracking()
                        .Select(x => new NamedRow(x.Id, x.Name)).ToListAsync();
                if (wantedTypes.Contains("wurf"))
                    byType["wurf"] = await db.Litters.AsNoTracking()
                        .Select(x => new NamedRow(x.Id, x.Name)).ToListAsync();
                if (wantedTypes.Contains("gehege"))
                    byType["gehege"] = await db.Enclosures.AsNoTracking()
                        .Select(x => new NamedRow(x.Id, x.Name)).ToListAsync();

                var results = new List<RefResolution>(refs.Count);
                foreach (var r in refs)
                {
                    var type = NormalizeType(r.Type);
                    var prefix = NormalizeCode(r.Code);

                    if (type is null || prefix.Length < 6 || !byType.TryGetValue(type, out var rows))
                    {
                        results.Add(new RefResolution(r.Type, r.Code, null, null));
                        continue;
                    }

                    // Präfix-Match auf der bindestrich-losen, klein geschriebenen GUID ("N"-Format).
                    var matches = rows
                        .Where(x => x.Id.ToString("N").StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                        .Take(2)
                        .ToList();

                    // Genau ein Treffer => auflösen; 0 (unbekannt) oder >1 (mehrdeutig) => null.
                    if (matches.Count == 1)
                        results.Add(new RefResolution(r.Type, r.Code, matches[0].Id, matches[0].Name));
                    else
                        results.Add(new RefResolution(r.Type, r.Code, null, null));
                }

                return TypedResults.Ok(results);
            });

            return app;
        }

        /// <summary>Akzeptiert die vier Typ-Schlüssel (case-insensitive); sonst null.</summary>
        private static string? NormalizeType(string? type) => type?.Trim().ToLowerInvariant() switch
        {
            "tier" => "tier",
            "kontakt" => "kontakt",
            "wurf" => "wurf",
            "gehege" => "gehege",
            _ => null,
        };

        /// <summary>GUID-Code bindestrich-tolerant + Hex-only normalisieren (Kleinschreibung).</summary>
        private static string NormalizeCode(string? code)
        {
            if (string.IsNullOrWhiteSpace(code)) return string.Empty;
            var chars = code.Trim().ToLowerInvariant()
                .Where(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
            return new string(chars.ToArray());
        }
    }
}
