using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using PolyTrader.Modules.Supervisor.Agent;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>
    /// Sicherheitsnetz für den S-2-Agenten: Tool-Registry (Ausführung, Fehler, Unbekanntes),
    /// Function-Calling-Loop (Tool-Ergebnisse fließen zurück, Iterationsgrenze) und die
    /// OpenRouter-Serialisierung (Request-Body pur, Response-Parsing).
    /// </summary>
    public class SupervisorAgentTests
    {
        // ----- Registry -----

        private static SupervisorToolRegistry RegistryWithEcho()
        {
            var reg = new SupervisorToolRegistry();
            reg.Register(new SupervisorTool("echo", "Echo",
                """{"type":"object","properties":{"text":{"type":"string"}}}""",
                args => "ECHO:" + (SupervisorToolRegistry.GetString(args, "text") ?? "")));
            reg.Register(new SupervisorTool("boom", "wirft",
                """{"type":"object","properties":{}}""",
                _ => throw new InvalidOperationException("kaputt")));
            return reg;
        }

        [Fact]
        public void Registry_executes_tool_with_args()
        {
            Assert.Equal("ECHO:hallo", RegistryWithEcho().Execute("echo", """{"text":"hallo"}"""));
        }

        [Fact]
        public void Registry_unknown_tool_and_broken_args_return_error_strings()
        {
            var reg = RegistryWithEcho();
            Assert.StartsWith("FEHLER: Unbekanntes Tool", reg.Execute("gibtsnicht", "{}"));
            Assert.StartsWith("FEHLER: Ungültige Tool-Argumente", reg.Execute("echo", "{kein json"));
            Assert.StartsWith("FEHLER bei Tool 'boom'", reg.Execute("boom", "{}"));   // Exception -> Text, wirft nie
        }

        // ----- Agent-Loop -----

        private sealed class ScriptedChatClient : IChatCompletionClient
        {
            private readonly Queue<ChatResponse> _script;
            public List<IReadOnlyList<ChatMessage>> Requests { get; } = new();
            public List<IReadOnlyList<SupervisorTool>> OfferedTools { get; } = new();

            public ScriptedChatClient(params ChatResponse[] script) => _script = new Queue<ChatResponse>(script);

            public Task<ChatResponse> CompleteAsync(string model, IReadOnlyList<ChatMessage> messages,
                IReadOnlyList<SupervisorTool> tools, CancellationToken ct)
            {
                Requests.Add(new List<ChatMessage>(messages));
                OfferedTools.Add(new List<SupervisorTool>(tools));
                return Task.FromResult(_script.Count > 0 ? _script.Dequeue() : new ChatResponse { Content = "leer" });
            }
        }

        [Fact]
        public async Task Agent_executes_tool_calls_and_feeds_results_back()
        {
            var chat = new ScriptedChatClient(
                new ChatResponse { ToolCalls = { new ToolCall("c1", "echo", """{"text":"daten"}""") } },
                new ChatResponse { Content = "Fertige Analyse." });
            var agent = new SupervisorAgent(chat, RegistryWithEcho());

            var result = await agent.AskAsync("Warum X?");

            Assert.Equal("Fertige Analyse.", result.Answer);
            Assert.Single(result.ToolInvocations);
            Assert.Equal(("echo", """{"text":"daten"}""", "ECHO:daten"), result.ToolInvocations[0]);

            // Zweiter Request enthält Assistant-ToolCall + Tool-Ergebnis.
            var second = chat.Requests[1];
            Assert.Contains(second, m => m.Role == "assistant" && m.ToolCalls is { Count: 1 });
            Assert.Contains(second, m => m.Role == "tool" && m.ToolCallId == "c1" && m.Content == "ECHO:daten");
            // System-Prompt enthält den Architektur-Kontext.
            Assert.Contains("ARCHITEKTUR-KONTEXT", second[0].Content);
        }

        [Fact]
        public async Task Agent_stops_at_max_iterations()
        {
            // Modell fordert ENDLOS Tools an -> Agent bricht kontrolliert ab.
            var endless = Enumerable.Range(0, SupervisorAgent.MaxIterations + 2)
                .Select(i => new ChatResponse { ToolCalls = { new ToolCall($"c{i}", "echo", "{}") } })
                .ToArray();
            var agent = new SupervisorAgent(new ScriptedChatClient(endless), RegistryWithEcho());

            var result = await agent.AskAsync("loop");

            Assert.Contains("maximale Tool-Iterationen", result.Answer);
            Assert.Equal(SupervisorAgent.MaxIterations, result.ToolInvocations.Count);
        }

        [Fact]
        public async Task Agent_aborts_when_token_budget_exceeded()
        {
            // Erste Antwort verbraucht bereits mehr Tokens als das Budget -> Abbruch VOR Tool-Ausführung.
            var chat = new ScriptedChatClient(
                new ChatResponse { PromptTokens = 80, CompletionTokens = 80,
                    ToolCalls = { new ToolCall("c1", "echo", "{}") } });
            var agent = new SupervisorAgent(chat, RegistryWithEcho());
            var profile = new SupervisorProfile("Knapp", "", null)
            {
                Budget = new PolyTrader.Core.Agents.AgentBudget { MaxTokens = 100, MaxSteps = 100 }
            };

            var result = await agent.AskAsync("x", profile: profile);

            Assert.Contains("Token-Budget erschöpft", result.Answer);
            Assert.Empty(result.ToolInvocations);
        }

        // ----- OpenRouter-Serialisierung (pur) -----

        [Fact]
        public void BuildRequestBody_produces_openai_compatible_json()
        {
            var messages = new List<ChatMessage>
            {
                ChatMessage.System("sys"),
                ChatMessage.User("frage"),
                ChatMessage.Assistant(null, new List<ToolCall> { new("c1", "echo", """{"text":"x"}""") }),
                ChatMessage.ToolResult("c1", "ergebnis")
            };
            var tools = new List<SupervisorTool>
            {
                new("echo", "Echo-Tool", """{"type":"object","properties":{"text":{"type":"string"}}}""", _ => "")
            };

            string body = OpenRouterClient.BuildRequestBody("openrouter/auto", messages, tools);
            using var doc = JsonDocument.Parse(body);   // valides JSON
            var root = doc.RootElement;

            Assert.Equal("openrouter/auto", root.GetProperty("model").GetString());
            var msgs = root.GetProperty("messages");
            Assert.Equal(4, msgs.GetArrayLength());
            Assert.Equal("function", msgs[2].GetProperty("tool_calls")[0].GetProperty("type").GetString());
            Assert.Equal("echo", msgs[2].GetProperty("tool_calls")[0].GetProperty("function").GetProperty("name").GetString());
            Assert.Equal("c1", msgs[3].GetProperty("tool_call_id").GetString());
            Assert.Equal("echo", root.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
        }

        [Fact]
        public void ParseResponse_reads_content_toolcalls_and_usage()
        {
            const string json = """
            {"choices":[{"message":{"content":null,"tool_calls":[
                {"id":"call_1","type":"function","function":{"name":"query_trades","arguments":"{\"limit\":5}"}}]}}],
             "usage":{"prompt_tokens":120,"completion_tokens":30}}
            """;
            var r = OpenRouterClient.ParseResponse(json);

            Assert.Null(r.Content);
            Assert.Single(r.ToolCalls);
            Assert.Equal("query_trades", r.ToolCalls[0].Name);
            Assert.Equal("""{"limit":5}""", r.ToolCalls[0].ArgumentsJson);
            Assert.Equal(120, r.PromptTokens);
            Assert.Equal(30, r.CompletionTokens);
        }

        [Fact]
        public void ArchitectureContext_is_embedded_and_loads()
        {
            string ctx = ArchitectureContext.Load();
            Assert.Contains("PolyTrader", ctx);
            Assert.Contains("read-only", ctx);
        }

        // ----- Profile (S-3) -----

        [Fact]
        public async Task Technik_profile_filters_tools_and_extends_prompt()
        {
            var reg = RegistryWithEcho();
            reg.Register(new SupervisorTool("read_logs", "Logs", """{"type":"object","properties":{}}""", _ => "logs"));
            var chat = new ScriptedChatClient(new ChatResponse { Content = "ok" });
            var agent = new SupervisorAgent(chat, reg);

            await agent.AskAsync("check", profile: SupervisorProfiles.Technik);

            // Prompt trägt den Profil-Fokus; dem Modell wurde NUR das Technik-Subset angeboten
            // (Registry hat echo/boom/read_logs → im Technik-Filter ist davon nur read_logs).
            Assert.Contains("FOKUS TECHNIK-SUPERVISOR", chat.Requests[0][0].Content);
            Assert.Single(chat.OfferedTools[0]);
            Assert.Equal("read_logs", chat.OfferedTools[0][0].Name);
        }

        [Fact]
        public void Profiles_lookup_is_case_insensitive_with_fallback()
        {
            Assert.Equal("Technik", SupervisorProfiles.ByName("technik").Name);
            Assert.Equal("Allgemein", SupervisorProfiles.ByName("gibtsnicht").Name);
            Assert.Equal(4, SupervisorProfiles.All.Count);
        }
    }
}
