using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Services;
using Xunit;

namespace Nexus.Api.Tests;

public sealed class AgentServiceTests
{
    [Fact]
    public async Task GetAgentsAsync_ProjectsOnlyLiveOpenClawInventory()
    {
        var service = new AgentService(new StubOpenClawControlService());

        var agents = await service.GetAgentsAsync(CancellationToken.None);

        Assert.Equal(6, agents.Count);
        Assert.Contains(agents, agent =>
            agent.Id == "product-owner" &&
            agent.Workspace == "/workspace-po" &&
            agent.Role == "Product Owner");
    }

    [Fact]
    public async Task GetAgentAsync_Iris_ReturnsOrchestrator()
    {
        var service = new AgentService(new StubOpenClawControlService());

        var agent = await service.GetAgentAsync("iris", CancellationToken.None);

        Assert.NotNull(agent);
        Assert.Equal("Orchestrator", agent.Role);
        Assert.Equal("/workspace/iris", agent.Workspace);
        Assert.Null(agent.AgentDir);
    }

    [Fact]
    public async Task GetAgentAsync_Unknown_ReturnsNull()
    {
        var service = new AgentService(new StubOpenClawControlService());

        var agent = await service.GetAgentAsync("nonexistent", CancellationToken.None);

        Assert.Null(agent);
    }

    [Fact]
    public async Task GetAllowedAgentIdsAsync_UsesLiveIds()
    {
        var control = new StubOpenClawControlService(
            agents:
            [
                Agent("custom-live", "Custom"),
                Agent("product-owner", "Product Owner")
            ]);
        var service = new AgentService(control);

        var ids = await service.GetAllowedAgentIdsAsync(CancellationToken.None);

        Assert.Equal(2, ids.Count);
        Assert.Contains("custom-live", ids);
        Assert.Contains("product-owner", ids);
        Assert.DoesNotContain("iris", ids);
    }

    [Fact]
    public async Task GetAgentAsync_LatestSessionModelOverridesInventoryModel()
    {
        var older = DateTimeOffset.UtcNow.AddMinutes(-10);
        var newer = DateTimeOffset.UtcNow;
        var control = new StubOpenClawControlService(
            agents: [Agent("programmer-fast", "Programmer Fast", "openai/default")],
            sessions:
            [
                Session("old", "programmer-fast", "openai/gpt-5.3", older),
                Session("new", "programmer-fast", "openai/gpt-5.5", newer)
            ]);
        var service = new AgentService(control);

        var agent = await service.GetAgentAsync("programmer-fast", CancellationToken.None);

        Assert.NotNull(agent);
        Assert.Equal("Developer", agent.Role);
        Assert.Equal("openai/gpt-5.5", agent.Model);
        Assert.Equal(newer, agent.LastSeen);
    }

    [Fact]
    public async Task GetAgentsAsync_DisconnectedGatewayMarksAgentsOffline()
    {
        var service = new AgentService(new StubOpenClawControlService(connected: false));

        var agents = await service.GetAgentsAsync(CancellationToken.None);

        Assert.All(agents, agent => Assert.Equal(OperationalStatus.Offline, agent.Status));
    }

    private static OpenClawAgentDto Agent(
        string id,
        string name,
        string model = "openai/gpt-5.5")
        => new(
            Id: id,
            Name: name,
            Description: null,
            Model: model,
            Provider: "openai",
            Workspace: $"/live/{id}",
            Status: "ready");

    private static OpenClawSessionDto Session(
        string key,
        string agentId,
        string model,
        DateTimeOffset updatedAt)
        => new(
            Key: key,
            SessionId: key,
            AgentId: agentId,
            Title: key,
            Status: "active",
            Kind: "agent",
            Channel: null,
            Model: model,
            Provider: "openai",
            RunId: null,
            UpdatedAt: updatedAt,
            InputTokens: null,
            OutputTokens: null,
            TotalTokens: null,
            CanAbort: true);
}
