using ClawdDotNet.Core.Accounting;
using ClawdDotNet.Core.Config;

namespace ClawdDotNet.Core.Deploymentcenter.Watchdog;

/// <summary>
/// Leitet den Instanz-Zustand für den Heartbeat ab.
///
/// <para>Ein Heartbeat allein beweist nur, dass ein Faden läuft. Deshalb geht der
/// selbst ermittelte Gesundheitszustand als <c>checks</c> mit — der Server stuft einen
/// als <c>ok</c> gemeldeten Beat herab, sobald eine Prüfung fehlschlägt, und nennt in
/// der Antwort die betroffene. Der klassische Fall, den das abfängt: Der Takt meldet
/// brav <c>ok</c>, während der Aufgaben-Scanner seit einer Stunde tot ist.</para>
///
/// <list type="bullet">
///   <item><c>error</c>   — kein OpenRouter-Key konfiguriert (Agenten deaktiviert).</item>
///   <item><c>warning</c> — Tagesbudget der Instanz erschöpft oder Scanner steht.</item>
///   <item><c>ok</c>      — sonst.</item>
/// </list>
///
/// <para>Die Metriken sind bewusst schlank und ausschließlich numerisch: keine
/// sensiblen Nutzdaten, und nur Zahlen landen im Verlauf.</para>
/// </summary>
public sealed class InstanceHealthProvider : IInstanceHealthProvider
{
    private readonly string _instanceName;
    private readonly bool _agentsEnabled;
    private readonly InstanceBudget _budget;
    private readonly IUsageRepository? _usage;
    private readonly Func<int> _agentCount;
    private readonly Func<int> _runningChats;
    private readonly Func<bool>? _schedulerRunning;
    private readonly Func<DateTime> _now;

    public InstanceHealthProvider(
        string instanceName,
        bool agentsEnabled,
        InstanceBudget budget,
        IUsageRepository? usage,
        Func<int> agentCount,
        Func<int> runningChats,
        Func<bool>? schedulerRunning = null,
        Func<DateTime>? now = null)
    {
        _instanceName = instanceName;
        _agentsEnabled = agentsEnabled;
        _budget = budget;
        _usage = usage;
        _agentCount = agentCount;
        _runningChats = runningChats;
        _schedulerRunning = schedulerRunning;
        _now = now ?? (() => DateTime.Now);
    }

    public async Task<InstanceHealth> GetAsync(CancellationToken ct)
    {
        var metrics = new Dictionary<string, double>
        {
            ["agentCount"] = _agentCount(),
            ["runningChats"] = _runningChats()
        };

        var checks = new Dictionary<string, HealthCheck>
        {
            ["agents"] = new(_agentsEnabled,
                _agentsEnabled ? null : "Kein OpenRouter-API-Key konfiguriert.")
        };

        if (_schedulerRunning is not null)
        {
            var running = _schedulerRunning();
            checks["scheduler"] = new(running,
                running ? null : "Aufgaben-Scanner läuft nicht.");
        }

        string? budgetProblem = null;

        if (_usage is not null)
        {
            var today = DateOnly.FromDateTime(_now());
            var used = await _usage.GetDailyAsync(today, agentId: "", ct).ConfigureAwait(false);

            metrics["todayCostUsd"] = (double)decimal.Round(used.CostUsd, 4);
            metrics["todayTokens"] = used.TotalTokens;

            if (Exceeds(_budget.DailyCostUsd, used.CostUsd))
            {
                budgetProblem =
                    $"Tagesbudget erschöpft: {used.CostUsd:F2} von {_budget.DailyCostUsd:F2} USD.";
            }
            else if (Exceeds(_budget.DailyTokens, used.TotalTokens))
            {
                budgetProblem =
                    $"Token-Tageslimit erschöpft: {used.TotalTokens:N0} von {_budget.DailyTokens:N0}.";
            }

            checks["budget"] = new(budgetProblem is null, budgetProblem);
        }

        // Der Instanzname steht in der Meldung, nicht in den Metriken: Metriken sind
        // Zahlen, alles andere würde der Server beim Verdichten ohnehin verwerfen.
        if (!_agentsEnabled)
        {
            return new InstanceHealth(
                WatchdogStatus.Error,
                $"{_instanceName}: Kein OpenRouter-API-Key konfiguriert – Agenten deaktiviert.",
                metrics, checks);
        }

        if (budgetProblem is not null)
            return new InstanceHealth(WatchdogStatus.Warning, $"{_instanceName}: {budgetProblem}", metrics, checks);

        if (_schedulerRunning is not null && !_schedulerRunning())
        {
            return new InstanceHealth(
                WatchdogStatus.Warning,
                $"{_instanceName}: Aufgaben-Scanner läuft nicht.",
                metrics, checks);
        }

        return new InstanceHealth(WatchdogStatus.Ok, $"{_instanceName}: Betrieb normal.", metrics, checks);
    }

    /// <summary>0 oder kleiner bedeutet: keine Grenze gesetzt. Deckungsgleich mit BudgetGuard.</summary>
    private static bool Exceeds(decimal limit, decimal used) => limit > 0 && used >= limit;

    private static bool Exceeds(long limit, long used) => limit > 0 && used >= limit;
}
