using System.Net.Http.Json;
using System.Text.Json;
using ClawdDotNet.Core.Tools;
using Microsoft.Extensions.Logging;

namespace ClawdDotNet.Tools.DirectAPI;

public sealed class DirectApiTool : IAgentTool
{
    public string Name => "DirectAPI";

    public string Description => """
        Ruft Echtzeit-Finanzdaten von verifizierten APIs ab.
        Alle Antworten enthalten fetchedAt und dataAsOf Timestamps.
        Aktionen: quote, history, crypto, forex, search
        """;

    public JsonElement InputSchema { get; } = JsonDocument.Parse("""
    {
      "type": "object",
      "required": ["action", "symbol"],
      "properties": {
        "action": {
          "type": "string",
          "enum": ["quote", "history", "crypto", "forex", "search"],
          "description": "quote=aktueller Kurs, history=Kursverlauf, crypto=Krypto, forex=Wechselkurs, search=Symbol suchen"
        },
        "symbol":   { "type": "string", "description": "z.B. NVDA, BTC, EUR/USD" },
        "provider": { "type": "string", "description": "optional: twelvedata|alphavantage|massive|coingecko|yahoo" },
        "interval": { "type": "string", "description": "für history: 1min|5min|1h|1day" },
        "outputsize":{ "type": "integer","description": "für history: Anzahl Datenpunkte, max 500" }
      }
    }
    """).RootElement.Clone();

    public async Task<ToolResult> ExecuteAsync(
        JsonElement input,
        AgentToolContext context,
        CancellationToken ct)
    {
        try
        {
            var action = input.GetProperty("action").GetString()!;
            var symbol = input.GetProperty("symbol").GetString()!;

            // Config laden
            IReadOnlyDictionary<string, object?>? config = context.ToolConfig.TryGetValue("DirectAPI", out var c) && c is JsonElement je 
                ? JsonSerializer.Deserialize<Dictionary<string, object?>>(je.GetRawText()) 
                : context.ToolConfig;

            if (config == null) return ToolResult.Fail("DirectAPI Konfiguration fehlt.");

            var providers = config.GetValueOrDefault("providers") as JsonElement?;
            var defaultProvider = config.GetValueOrDefault("defaultProvider")?.ToString() ?? "twelvedata";
            var provider = input.TryGetProperty("provider", out var p) ? p.GetString() : defaultProvider;

            return action switch
            {
                "quote" => await FetchQuoteAsync(symbol, provider!, providers, context, ct),
                "history" => await FetchHistoryAsync(symbol, input, provider!, providers, context, ct),
                "crypto" => await FetchCryptoAsync(symbol, providers, context, ct),
                "forex" => await FetchForexAsync(symbol, provider!, providers, context, ct),
                "search" => await SearchSymbolAsync(symbol, provider!, providers, context, ct),
                _ => ToolResult.Fail($"Unbekannte Aktion: {action}")
            };
        }
        catch (Exception ex)
        {
            context.Logger.LogError(ex, "Fehler in DirectAPI");
            return ToolResult.Fail($"Fehler: {ex.Message}");
        }
    }

    private async Task<ToolResult> FetchQuoteAsync(string symbol, string provider, JsonElement? providers, AgentToolContext context, CancellationToken ct)
    {
        using var http = new HttpClient();
        http.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
        var fetchedAt = DateTime.UtcNow;

        if (provider == "twelvedata")
        {
            var apiKey = GetApiKey(providers, "twelvedata");
            var url = $"https://api.twelvedata.com/quote?symbol={symbol}&apikey={apiKey}";
            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            if (json.Value.TryGetProperty("code", out var code) && code.GetInt32() != 200)
                return ToolResult.Fail(json.Value.GetProperty("message").GetString() ?? "API Fehler");

            DateTime? dataAsOf = null;
            if (json.Value.TryGetProperty("timestamp", out var ts))
                dataAsOf = DateTimeOffset.FromUnixTimeSeconds(ts.GetInt64()).UtcDateTime;

            return CreateSuccessResult(fetchedAt, dataAsOf, url, json.Value);
        }
        else if (provider == "alphavantage")
        {
            var apiKey = GetApiKey(providers, "alphavantage");
            var url = $"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apiKey}";
            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            if (json.Value.TryGetProperty("Note", out var note))
                return ToolResult.Fail($"Alpha Vantage Rate Limit: {note.GetString()}");
            if (json.Value.TryGetProperty("Error Message", out var err))
                return ToolResult.Fail($"Alpha Vantage Fehler: {err.GetString()}");

            return CreateSuccessResult(fetchedAt, fetchedAt, url, json.Value);
        }
        else if (provider == "massive")
        {
            var apiKey = GetApiKey(providers, "massive");
            var url = $"https://api.massive.com/v3/snapshot?ticker={symbol}";
            http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            return CreateSuccessResult(fetchedAt, fetchedAt, url, json.Value);
        }
        else if (provider == "yahoo")
        {
            var url = $"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1m&range=1d";
            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            if (!json.Value.TryGetProperty("chart", out var chartProp) ||
                !chartProp.TryGetProperty("result", out var resultArr) ||
                resultArr.GetArrayLength() == 0)
                return ToolResult.Fail($"Yahoo Finance: Keine Daten für '{symbol}' erhalten.");

            var chart = resultArr[0];
            var meta = chart.GetProperty("meta");

            DateTime? dataAsOf = null;
            if (meta.TryGetProperty("regularMarketTime", out var ts))
                dataAsOf = DateTimeOffset.FromUnixTimeSeconds(ts.GetInt64()).UtcDateTime;

            return CreateSuccessResult(fetchedAt, dataAsOf, url, chart);
        }

        return ToolResult.Fail($"Provider '{provider}' wird für 'quote' noch nicht unterstützt.");
    }

    private async Task<ToolResult> FetchHistoryAsync(string symbol, JsonElement input, string provider, JsonElement? providers, AgentToolContext context, CancellationToken ct)
    {
        var interval = input.TryGetProperty("interval", out var i) ? i.GetString() : "1day";
        var outputSize = input.TryGetProperty("outputsize", out var o) ? o.GetInt32() : 30;
        using var http = new HttpClient();
        var fetchedAt = DateTime.UtcNow;

        if (provider == "twelvedata")
        {
            var apiKey = GetApiKey(providers, "twelvedata");
            var url = $"https://api.twelvedata.com/time_series?symbol={symbol}&interval={interval}&outputsize={outputSize}&apikey={apiKey}";

            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            if (json.Value.TryGetProperty("status", out var status) && status.GetString() != "ok")
                return ToolResult.Fail(json.Value.GetProperty("message").GetString() ?? "API Fehler");

            return CreateSuccessResult(fetchedAt, null, url, json.Value);
        }
        else if (provider == "alphavantage")
        {
            var apiKey = GetApiKey(providers, "alphavantage");
            var function_ = interval switch
            {
                "1min" or "5min" or "15min" or "30min" or "1h" => "TIME_SERIES_INTRADAY",
                _ => "TIME_SERIES_DAILY"
            };
            var avInterval = interval switch
            {
                "1min" => "1min", "5min" => "5min", "15min" => "15min", "30min" => "30min", "1h" => "60min",
                _ => ""
            };
            var urlBase = $"https://www.alphavantage.co/query?function={function_}&symbol={symbol}&apikey={apiKey}&outputsize=compact";
            if (!string.IsNullOrEmpty(avInterval)) urlBase += $"&interval={avInterval}";

            var (json, error) = await SafeGetJsonAsync(http, urlBase, ct);
            if (error != null) return error;

            if (json.Value.TryGetProperty("Note", out var note))
                return ToolResult.Fail($"Alpha Vantage Rate Limit: {note.GetString()}");
            if (json.Value.TryGetProperty("Error Message", out var err))
                return ToolResult.Fail($"Alpha Vantage Fehler: {err.GetString()}");

            return CreateSuccessResult(fetchedAt, null, urlBase, json.Value);
        }
        else if (provider == "massive")
        {
            var apiKey = GetApiKey(providers, "massive");
            var timespan = interval switch
            {
                "1min" => "minute", "5min" => "minute", "15min" => "minute", "1h" => "hour", "1day" => "day",
                _ => "day"
            };
            var multiplier = interval switch
            {
                "5min" => 5, "15min" => 15, _ => 1
            };
            var from = DateTime.UtcNow.AddDays(-outputSize).ToString("yyyy-MM-dd");
            var to = DateTime.UtcNow.ToString("yyyy-MM-dd");
            var url = $"https://api.massive.com/v2/aggs/ticker/{symbol}/range/{multiplier}/{timespan}/{from}/{to}?limit={outputSize}";
            http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");

            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            return CreateSuccessResult(fetchedAt, null, url, json.Value);
        }
        else if (provider == "yahoo")
        {
            var yahooInterval = interval switch
            {
                "1min" => "1m", "5min" => "5m", "15min" => "15m", "1h" => "1h", "1day" => "1d",
                _ => "1d"
            };
            var range = interval is "1min" or "5min" or "15min" ? "1d" : $"{outputSize}d";
            var url = $"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval={yahooInterval}&range={range}";

            var (json, error) = await SafeGetJsonAsync(http, url, ct);
            if (error != null) return error;

            if (!json.Value.TryGetProperty("chart", out var chartProp) ||
                !chartProp.TryGetProperty("result", out var resultArr) ||
                resultArr.GetArrayLength() == 0)
                return ToolResult.Fail($"Yahoo Finance: Keine Historien-Daten für '{symbol}' erhalten.");

            var chart = resultArr[0];
            return CreateSuccessResult(fetchedAt, null, url, chart);
        }

        return ToolResult.Fail($"Provider '{provider}' wird für 'history' noch nicht unterstützt.");
    }

    private async Task<ToolResult> FetchCryptoAsync(string symbol, JsonElement? providers, AgentToolContext context, CancellationToken ct)
    {
        using var http = new HttpClient();
        var fetchedAt = DateTime.UtcNow;
        var url = $"https://api.coingecko.com/api/v3/simple/price?ids={symbol.ToLowerInvariant()}&vs_currencies=usd&include_last_updated_at=true";
        
        var (json, error) = await SafeGetJsonAsync(http, url, ct);
        if (error != null) return error;

        if (!json.Value.TryGetProperty(symbol.ToLowerInvariant(), out var data))
             return ToolResult.Fail($"Symbol '{symbol}' nicht gefunden.");

        DateTime? dataAsOf = null;
        if (data.TryGetProperty("last_updated_at", out var ts))
            dataAsOf = DateTimeOffset.FromUnixTimeSeconds(ts.GetInt64()).UtcDateTime;

        return CreateSuccessResult(fetchedAt, dataAsOf, url, data);
    }

    private async Task<ToolResult> FetchForexAsync(string symbol, string provider, JsonElement? providers, AgentToolContext context, CancellationToken ct)
    {
        if (provider != "twelvedata") return ToolResult.Fail("Forex wird aktuell nur über twelvedata unterstützt.");
        
        var apiKey = GetApiKey(providers, "twelvedata");
        using var http = new HttpClient();
        var fetchedAt = DateTime.UtcNow;
        var url = $"https://api.twelvedata.com/exchange_rate?symbol={symbol}&apikey={apiKey}";
        
        var (json, error) = await SafeGetJsonAsync(http, url, ct);
        if (error != null) return error;

        DateTime? dataAsOf = null;
        if (json.Value.TryGetProperty("timestamp", out var ts))
            dataAsOf = DateTimeOffset.FromUnixTimeSeconds(ts.GetInt64()).UtcDateTime;

        return CreateSuccessResult(fetchedAt, dataAsOf, url, json.Value);
    }

    private async Task<ToolResult> SearchSymbolAsync(string symbol, string provider, JsonElement? providers, AgentToolContext context, CancellationToken ct)
    {
         if (provider != "twelvedata") return ToolResult.Fail("Search wird aktuell nur über twelvedata unterstützt.");
         
         var apiKey = GetApiKey(providers, "twelvedata");
         using var http = new HttpClient();
         var fetchedAt = DateTime.UtcNow;
         var url = $"https://api.twelvedata.com/symbol_search?symbol={symbol}"; // search often doesn't need key or uses same
         
         var (json, error) = await SafeGetJsonAsync(http, url, ct);
         if (error != null) return error;
         return CreateSuccessResult(fetchedAt, null, url, json.Value);
    }

    /// <summary>
    /// Sichere JSON-Antwort von einer API abrufen.
    /// Prüft HTTP-Status und ob die Antwort tatsächlich JSON ist, bevor geparst wird.
    /// </summary>
    private static async Task<(JsonElement? json, ToolResult? error)> SafeGetJsonAsync(
        HttpClient http, string url, CancellationToken ct)
    {
        // Auch Fehlermeldungen gehen an das Modell — der Schlüssel darf darin nicht auftauchen.
        var safeUrl = UrlSanitizer.Sanitize(url);

        HttpResponseMessage response;
        try
        {
            response = await http.GetAsync(url, ct);
        }
        catch (HttpRequestException ex)
        {
            return (null, ToolResult.Fail($"API nicht erreichbar: {safeUrl} → {ex.Message}"));
        }

        var body = await response.Content.ReadAsStringAsync(ct);

        if (!response.IsSuccessStatusCode)
        {
            var preview = body.Length > 300 ? body[..300] + "…" : body;
            return (null, ToolResult.Fail(
                $"API Fehler: HTTP {(int)response.StatusCode} {response.ReasonPhrase} von {safeUrl}\nAntwort: {preview}"));
        }

        // Prüfen ob die Antwort überhaupt JSON ist
        var trimmed = body.TrimStart();
        if (trimmed.Length == 0 || (trimmed[0] != '{' && trimmed[0] != '['))
        {
            var preview = body.Length > 300 ? body[..300] + "…" : body;
            return (null, ToolResult.Fail(
                $"API hat kein JSON zurückgegeben ({safeUrl}). Antwort: {preview}"));
        }

        try
        {
            var json = JsonDocument.Parse(body).RootElement.Clone();
            return (json, null);
        }
        catch (JsonException ex)
        {
            var preview = body.Length > 300 ? body[..300] + "…" : body;
            return (null, ToolResult.Fail(
                $"Ungültiges JSON von {safeUrl}: {ex.Message}\nAntwort: {preview}"));
        }
    }

    private string? GetApiKey(JsonElement? providers, string name)
    {
        if (providers == null) return null;
        if (providers.Value.TryGetProperty(name, out var p) && p.TryGetProperty("apiKey", out var k))
            return k.GetString();
        return null;
    }

    private ToolResult CreateSuccessResult(DateTime fetchedAt, DateTime? dataAsOf, string source, object data)
    {
        var result = new
        {
            fetchedAt = fetchedAt,
            dataAsOf = dataAsOf,
            // Ohne Bereinigung ginge der API-Schlüssel im Query-String an das Modell,
            // in den persistierten Kontext und in die Logs.
            source = UrlSanitizer.Sanitize(source),
            data = data
        };
        return ToolResult.Ok(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
    }
}
