using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PdfSharp.Fonts;

namespace PolyTrader.Modules.Accounting.Logic
{
    /// <summary>
    /// Schriftauflösung für den PDF-Export. Nötig, seit der plattformneutrale PDFsharp-Core-Build
    /// verwendet wird: der frühere <c>-GDI</c>-Build zog Systemschriften über
    /// <c>System.Drawing.Common</c>, das seit .NET 7 Windows-only ist. Der Core-Build bringt keine
    /// eigene Schriftsuche mit und braucht deshalb diesen Resolver.
    ///
    /// <para>Der Resolver durchsucht die üblichen Schriftverzeichnisse des Betriebssystems nach einer
    /// Kandidatenliste (Windows: Segoe UI/Arial, Linux: DejaVu/Liberation/Noto). Damit sind keine
    /// Schriftdateien im Repository nötig und die Lizenzfrage der Schriften bleibt beim System.</para>
    ///
    /// <para><b>Voraussetzung auf Linux:</b> mindestens eine der Kandidatenschriften muss installiert
    /// sein. Auf Desktop-Distributionen ist das der Normalfall; auf schlanken Servern/Containern
    /// genügt <c>apt install fonts-dejavu-core</c> (bzw. <c>fonts-liberation</c>). Fehlt jede Schrift,
    /// wirft <see cref="EnsureRegistered"/> beim ersten Export eine Meldung mit genau diesem Hinweis –
    /// besser als eine kryptische Meldung aus dem Renderer-Inneren.</para>
    /// </summary>
    public sealed class PdfFontResolver : IFontResolver
    {
        /// <summary>Bevorzugte Schriftfamilien in Reihenfolge – erste gefundene gewinnt.</summary>
        private static readonly string[] PreferredFamilies =
        {
            "Segoe UI", "DejaVu Sans", "Liberation Sans", "Noto Sans", "Arial", "FreeSans"
        };

        /// <summary>Dateinamens-Kandidaten je Familie und Schnitt (klein geschrieben, ohne Endung).</summary>
        private static readonly Dictionary<string, (string Regular, string Bold)> FileNames = new(StringComparer.OrdinalIgnoreCase)
        {
            ["Segoe UI"] = ("segoeui", "segoeuib"),
            ["DejaVu Sans"] = ("DejaVuSans", "DejaVuSans-Bold"),
            ["Liberation Sans"] = ("LiberationSans-Regular", "LiberationSans-Bold"),
            ["Noto Sans"] = ("NotoSans-Regular", "NotoSans-Bold"),
            ["Arial"] = ("arial", "arialbd"),
            ["FreeSans"] = ("FreeSans", "FreeSansBold"),
        };

        private static readonly ConcurrentDictionary<string, byte[]> Cache = new();
        private static readonly object RegisterLock = new();
        private static bool _registered;

        /// <summary>
        /// Registriert den Resolver einmalig global. Muss vor dem ersten Rendern laufen; mehrfache
        /// Aufrufe sind unschädlich (PDFsharp erlaubt nur eine Zuweisung pro Prozess).
        /// </summary>
        public static void EnsureRegistered()
        {
            if (_registered) return;
            lock (RegisterLock)
            {
                if (_registered) return;
                if (FindAnyAvailableFamily() == null)
                {
                    throw new InvalidOperationException(
                        "Keine geeignete Schriftart gefunden. Der PDF-Export braucht mindestens eine von: " +
                        string.Join(", ", PreferredFamilies) + ". " +
                        "Auf schlanken Linux-Systemen nachinstallieren, z.B. 'apt install fonts-dejavu-core'.");
                }
                GlobalFontSettings.FontResolver = new PdfFontResolver();
                _registered = true;
            }
        }

        /// <summary>Erste verfügbare Familie aus <see cref="PreferredFamilies"/>, sonst <c>null</c>.</summary>
        internal static string? FindAnyAvailableFamily() =>
            PreferredFamilies.FirstOrDefault(f => LocateFile(f, bold: false) != null);

        public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
        {
            // Angeforderte Familie bevorzugen, sonst die erste verfügbare (z.B. "Segoe UI" auf Linux).
            string? family = LocateFile(familyName, isBold) != null ? familyName : FindAnyAvailableFamily();
            if (family == null) return null;

            // Kursiv wird bewusst nicht aufgelöst: der Bericht nutzt es nicht, und ein fehlender
            // Kursiv-Schnitt wäre sonst ein harter Fehler statt einer kleinen Abweichung.
            return new FontResolverInfo($"{family}|{(isBold ? "b" : "r")}");
        }

        public byte[]? GetFont(string faceName)
        {
            return Cache.GetOrAdd(faceName, key =>
            {
                var parts = key.Split('|');
                string family = parts[0];
                bool bold = parts.Length > 1 && parts[1] == "b";

                string? path = LocateFile(family, bold) ?? LocateFile(family, bold: false);
                return path != null ? File.ReadAllBytes(path) : Array.Empty<byte>();
            });
        }

        /// <summary>Sucht die Datei zu Familie + Schnitt in allen Schriftverzeichnissen des Systems.</summary>
        private static string? LocateFile(string family, bool bold)
        {
            if (!FileNames.TryGetValue(family, out var names)) return null;
            string stem = bold ? names.Bold : names.Regular;

            foreach (string dir in FontDirectories())
            {
                if (!Directory.Exists(dir)) continue;
                foreach (string ext in new[] { ".ttf", ".otf" })
                {
                    try
                    {
                        // Rekursiv, weil Linux die Schriften in Unterordnern ablegt (truetype/dejavu/…).
                        var hit = Directory.EnumerateFiles(dir, stem + ext, SearchOption.AllDirectories)
                                           .FirstOrDefault();
                        if (hit != null) return hit;
                    }
                    catch (UnauthorizedAccessException) { /* einzelne Ordner ohne Zugriff überspringen */ }
                    catch (IOException) { /* z.B. defekter Symlink */ }
                }
            }
            return null;
        }

        /// <summary>Schriftverzeichnisse je Plattform.</summary>
        private static IEnumerable<string> FontDirectories()
        {
            if (OperatingSystem.IsWindows())
            {
                yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts");
                yield break;
            }

            if (OperatingSystem.IsMacOS())
            {
                yield return "/System/Library/Fonts";
                yield return "/Library/Fonts";
                yield break;
            }

            // Linux/Unix
            yield return "/usr/share/fonts";
            yield return "/usr/local/share/fonts";
            string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
            if (!string.IsNullOrEmpty(home))
            {
                yield return Path.Combine(home, ".fonts");
                yield return Path.Combine(home, ".local", "share", "fonts");
            }
        }
    }
}
