using System.Text.Json;
using ClawdDotNet.Core.State;
using ClawdDotNet.Core.Tasks;
using ClawdDotNet.Core.Tests.Infrastructure;
using ClawdDotNet.Core.Tools;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Tasks;

/// <summary>
/// Der tool_job-Dispatch — ersetzt den ToolJobScheduler. Ein fälliger Poll-Task tickt einen
/// <see cref="IToolJobProvider"/> und weckt den Agenten nur, wenn der Tick etwas meldet.
/// </summary>
public sealed class ToolJobDispatchTests
{
    private static TaskItem PollTask(string agent = "agent-a") => new()
    {
        Id = "tj-1",
        Title = "Poll",
        Type = TaskItemType.ToolJob,
        ToolName = "PollTool",
        JobTypeId = "poll",
        Assignee = "@" + agent
    };

    private static EngineTaskDispatcher Dispatcher(EngineFixture fixture)
        => new(fixture.Engine, () => fixture.Agents, "test-instance",
               fixture.Registry, fixture.StateStore, NullLoggerFactory.Instance);

    [Fact]
    public async Task Ein_Poll_ohne_Fund_weckt_den_Agenten_nicht()
    {
        var tool = new FakeJobTool(ToolJobResult.NoAction("nichts Neues"));
        var fixture = new EngineFixture().WithTool(tool);
        fixture.AddAgent("agent-a", "PollTool");

        var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default);

        ok.ShouldBeTrue("ein Poll ohne Fund ist kein Fehler");
        tool.Ticks.ShouldBe(1);
        fixture.Client.ReceivedRequests.ShouldBeEmpty("kein Agent-Lauf, wenn nichts zu wecken war");
    }

    [Fact]
    public async Task Ein_Poll_mit_Fund_weckt_den_Agenten()
    {
        var tool = new FakeJobTool(ToolJobResult.Wake("Neue Nachricht eingetroffen"));
        var fixture = new EngineFixture().WithTool(tool);
        fixture.AddAgent("agent-a", "PollTool");
        fixture.Client.RespondsWithText("verarbeitet");

        var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default);

        ok.ShouldBeTrue();
        tool.Ticks.ShouldBe(1);
        fixture.Client.ReceivedRequests.ShouldNotBeEmpty("der Agent wurde geweckt");
    }

    [Fact]
    public async Task Ein_unbekanntes_Poll_Tool_meldet_einen_Fehler()
    {
        var fixture = new EngineFixture(); // PollTool nicht registriert
        fixture.AddAgent("agent-a");

        (await Dispatcher(fixture).DispatchAsync(PollTask(), default)).ShouldBeFalse();
    }

    /// <summary>Ein Tool, das zugleich Poll-Provider ist — zählt Ticks, liefert ein festes Ergebnis.</summary>
    private sealed class FakeJobTool(ToolJobResult result) : IAgentTool, IToolJobProvider
    {
        public int Ticks { get; private set; }

        public string Name => "PollTool";
        public string Description => "Test-Poll";
        public JsonElement InputSchema { get; } =
            JsonDocument.Parse("""{ "type": "object" }""").RootElement.Clone();

        public Task<ToolResult> ExecuteAsync(JsonElement input, AgentToolContext context, CancellationToken ct)
            => Task.FromResult(ToolResult.Ok("ok"));

        public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() => [];

        public Task<ToolJobResult> ExecuteJobAsync(
            string jobTypeId, IReadOnlyDictionary<string, object?> toolConfig, IStateStore stateStore,
            ILogger logger, CancellationToken ct, string? agentId = null, string? workspacePath = null)
        {
            Ticks++;
            return Task.FromResult(result);
        }
    }
}
