namespace ClawdDotNet.Core.Scheduling;

/// <summary>
/// Deutet Zeitzonen-Kennungen unabhängig davon, auf welchem Betriebssystem sie
/// geschrieben wurden.
///
/// Hintergrund (Linux-Portierung): Zeitzonen werden auf Windows und Linux
/// unterschiedlich benannt — <c>"W. Europe Standard Time"</c> gegen
/// <c>"Europe/Berlin"</c>. Task-Dateien sind Markdown im geteilten Arbeitsverzeichnis
/// und wandern zwischen Rechnern. Eine Kennung, die auf dem einen System entstanden ist,
/// muss auf dem anderen lesbar bleiben.
///
/// Zuvor fing <c>TaskSchedule</c> die unbekannte Kennung ab und rechnete <b>still</b>
/// in UTC weiter. Ein Task, der um 08:00 Ortszeit laufen sollte, lief damit im Sommer
/// um 06:00 — ohne Meldung, ohne Logeintrag. Deshalb hier: beide Schreibweisen deuten,
/// und was sich nicht deuten lässt, meldet <see cref="TryResolve"/> als <c>null</c>
/// zurück, statt es zu erraten.
///
/// <para><b>Voraussetzung auf dem Zielsystem:</b> Die Umsetzung zwischen beiden
/// Schreibweisen kommt aus den ICU-Daten, die Zeitzonen selbst aus <c>tzdata</c>. In
/// einem schlanken Abbild (Alpine ohne <c>icu-libs</c>, distroless) oder bei
/// <c>InvariantGlobalization=true</c> fehlen sie — dann schlägt jede Auflösung außer
/// UTC fehl. Beides gehört ins Abbild.</para>
/// </summary>
public static class TimeZones
{
    /// <summary>
    /// Die lokale Zeitzone in IANA-Schreibweise (<c>Europe/Berlin</c>).
    ///
    /// Das ist die Form, die in Task-Dateien geschrieben werden soll: Sie gilt auf
    /// Linux, macOS und — seit .NET 8 — auch auf Windows.
    /// </summary>
    public static string LocalIanaId => ToIana(TimeZoneInfo.Local.Id);

    /// <summary>
    /// Löst eine Kennung auf, gleich ob IANA- oder Windows-Schreibweise.
    /// Gibt <c>null</c> zurück, wenn sie auf diesem System nicht auflösbar ist —
    /// der Aufrufer muss dann entscheiden, und zwar sichtbar.
    /// </summary>
    public static TimeZoneInfo? TryResolve(string? id)
    {
        if (string.IsNullOrWhiteSpace(id)) return null;

        var value = id.Trim();

        if (string.Equals(value, "UTC", StringComparison.OrdinalIgnoreCase))
            return TimeZoneInfo.Utc;

        // Direkt versuchen: .NET nimmt je nach Version und Plattform bereits beide
        // Formen an. Wenn das reicht, sind wir fertig.
        try { return TimeZoneInfo.FindSystemTimeZoneById(value); }
        catch (TimeZoneNotFoundException) { }
        catch (InvalidTimeZoneException) { }

        // Sonst die jeweils andere Schreibweise versuchen.
        if (TimeZoneInfo.TryConvertWindowsIdToIanaId(value, out var iana))
        {
            try { return TimeZoneInfo.FindSystemTimeZoneById(iana); }
            catch (TimeZoneNotFoundException) { }
            catch (InvalidTimeZoneException) { }
        }

        if (TimeZoneInfo.TryConvertIanaIdToWindowsId(value, out var windows))
        {
            try { return TimeZoneInfo.FindSystemTimeZoneById(windows); }
            catch (TimeZoneNotFoundException) { }
            catch (InvalidTimeZoneException) { }
        }

        return null;
    }

    /// <summary>
    /// Ob die Kennung auf diesem System auflösbar ist. Leer gilt als gültig — das
    /// bedeutet „keine Angabe" und wird vom Aufrufer als UTC gedeutet.
    /// </summary>
    public static bool IsKnown(string? id)
        => string.IsNullOrWhiteSpace(id) || TryResolve(id) is not null;

    /// <summary>
    /// Bringt eine Kennung auf IANA-Schreibweise. Lässt sie sich nicht umsetzen, kommt
    /// sie unverändert zurück — eine unbekannte Kennung zu verfälschen wäre schlimmer,
    /// als sie durchzureichen.
    /// </summary>
    public static string ToIana(string? id)
    {
        if (string.IsNullOrWhiteSpace(id)) return "";

        var value = id.Trim();

        // Enthält einen Schrägstrich → bereits IANA (Windows-Kennungen haben keinen).
        if (value.Contains('/')) return value;

        return TimeZoneInfo.TryConvertWindowsIdToIanaId(value, out var iana) ? iana : value;
    }
}
