using System.IO.Compression;
using GerbilManagerWebAPI.Dtos;
using GerbilManagerWebAPI.Import;
using GerbilManagerWebAPI.Import.Rpro3;
using Microsoft.AspNetCore.Http.HttpResults;

namespace GerbilManagerWebAPI.Endpoints
{
    /// <summary>
    /// FEAT-8 stage 3 — spreadsheet import loader. Consumes tools/import/output.
    ///   POST /import/dry-run  -> report what WOULD be created/linked/quarantined (no writes)
    ///   POST /import/execute  -> load the conflict-free subset (idempotent). GATED: only run
    ///                            against Julian's DB under god-supervision after he approves the dry-run.
    ///
    /// RPRO3 — vollwertiger RennmausPro-III-Backup-Importer (Upload via "Hilfe"):
    ///   POST /import/rpro3/analyze  -> .backup (+ optional bilder.zip) entpacken, parsen, deduplizieren;
    ///                                  schreibt NICHTS. Liefert Zählungen, Top-Merges, mehrdeutige Namen.
    ///   POST /import/rpro3/execute  -> idempotenter Import (deterministische GUIDs).
    /// </summary>
    public static class ImportEndpoints
    {
        public static IEndpointRouteBuilder MapImportEndpoints(this IEndpointRouteBuilder app)
        {
            var group = app.MapGroup("/import").WithTags("Import");

            group.MapPost("/dry-run", async Task<Ok<ImportReport>> (
                ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var report = await new ImportService(db, config, env).RunAsync(execute: false);
                return TypedResults.Ok(report);
            });

            group.MapPost("/execute", async Task<Ok<ImportReport>> (
                ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var report = await new ImportService(db, config, env).RunAsync(execute: true);
                return TypedResults.Ok(report);
            });

            group.MapPost("/ingest-resolved", async Task<Ok<string>> (
                ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                var result = await new IngestResolvedService(db, config, env).RunAsync();
                return TypedResults.Ok(result);
            });

            // ── RPRO3 ──────────────────────────────────────────────────────────
            group.MapPost("/rpro3/analyze", async Task<Results<Ok<Rpro3AnalyzeResult>, BadRequest<string>>> (
                IFormFile backup, IFormFile? images,
                ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                try
                {
                    var reader = new Rpro3Reader();
                    await using var bs = backup.OpenReadStream();
                    using var seekable = await ToSeekable(bs);
                    var data = reader.ReadFromBackup(seekable, out var workDir);
                    try
                    {
                        IReadOnlySet<string>? photoFiles = null;
                        if (images is not null)
                        {
                            await using var imgStream = images.OpenReadStream();
                            using var imgSeekable = await ToSeekable(imgStream);
                            photoFiles = ListZipImageNames(imgSeekable);
                        }
                        var service = new Rpro3ImportService(db, config, env);
                        var result = await service.AnalyzeAsync(data, images is not null, photoFiles);
                        return TypedResults.Ok(result);
                    }
                    finally { TryDeleteDir(workDir); }
                }
                catch (Rpro3FormatException ex) { return TypedResults.BadRequest(ex.Message); }
            }).WithTags("Import").DisableAntiforgery();

            group.MapPost("/rpro3/execute", async Task<Results<Ok<Rpro3ExecuteResult>, BadRequest<string>>> (
                IFormFile backup, IFormFile? images,
                ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
            {
                try
                {
                    var reader = new Rpro3Reader();
                    await using var bs = backup.OpenReadStream();
                    using var seekable = await ToSeekable(bs);
                    var data = reader.ReadFromBackup(seekable, out var workDir);
                    try
                    {
                        Dictionary<string, string>? photoPaths = null;
                        if (images is not null)
                        {
                            await using var imgStream = images.OpenReadStream();
                            using var imgSeekable = await ToSeekable(imgStream);
                            photoPaths = ExtractZipImages(imgSeekable, workDir);
                        }
                        var service = new Rpro3ImportService(db, config, env);
                        var result = await service.ExecuteAsync(data, workDir, images is not null, photoPaths);
                        return TypedResults.Ok(result);
                    }
                    finally { TryDeleteDir(workDir); }
                }
                catch (Rpro3FormatException ex) { return TypedResults.BadRequest(ex.Message); }
            }).WithTags("Import").DisableAntiforgery();

            return app;
        }

        // IFormFile-Streams sind nicht immer seekbar (ZipArchive braucht Seek) → in MemoryStream kopieren.
        private static async Task<MemoryStream> ToSeekable(Stream s)
        {
            var ms = new MemoryStream();
            await s.CopyToAsync(ms);
            ms.Position = 0;
            return ms;
        }

        private static readonly string[] ImageExts = { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp" };

        private static IReadOnlySet<string> ListZipImageNames(Stream zip)
        {
            var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            using var archive = new ZipArchive(zip, ZipArchiveMode.Read, leaveOpen: true);
            foreach (var e in archive.Entries)
                if (!string.IsNullOrEmpty(e.Name) && ImageExts.Contains(Path.GetExtension(e.Name).ToLowerInvariant()))
                    set.Add(e.Name);
            return set;
        }

        // Bilder ins workDir entpacken; Rückgabe: lowercase-Dateiname → absoluter Pfad.
        private static Dictionary<string, string> ExtractZipImages(Stream zip, string workDir)
        {
            var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            var imgDir = Path.Combine(workDir, "bilder");
            Directory.CreateDirectory(imgDir);
            using var archive = new ZipArchive(zip, ZipArchiveMode.Read, leaveOpen: true);
            foreach (var e in archive.Entries)
            {
                if (string.IsNullOrEmpty(e.Name) || !ImageExts.Contains(Path.GetExtension(e.Name).ToLowerInvariant()))
                    continue;
                var dest = Path.Combine(imgDir, Guid.NewGuid().ToString("N") + Path.GetExtension(e.Name));
                try { e.ExtractToFile(dest, overwrite: true); map[e.Name] = dest; }
                catch { /* defektes Archiv-Entry überspringen */ }
            }
            return map;
        }

        private static void TryDeleteDir(string dir)
        {
            try { if (Directory.Exists(dir)) Directory.Delete(dir, recursive: true); }
            catch { /* best effort */ }
        }
    }
}
