using Nexus.Api.Data;

namespace Nexus.Api.Services;

public sealed record AgentInfo(
    string Id,
    string Name,
    string Role,
    string Model,
    OperationalStatus Status,
    DateTimeOffset? LastSeen,
    string? Workspace,
    string? Description
);

public sealed record AgentDetail(
    string Id,
    string Name,
    string Role,
    string Model,
    OperationalStatus Status,
    DateTimeOffset? LastSeen,
    string? Workspace,
    string? AgentDir,
    string? Description,
    IReadOnlyList<string>? SubAgents,
    string? IdentityName
);

public interface IAgentService
{
    Task<IReadOnlyCollection<AgentInfo>> GetAgentsAsync(CancellationToken cancellationToken);
    Task<AgentDetail?> GetAgentAsync(string id, CancellationToken cancellationToken);
    Task<IReadOnlySet<string>> GetAllowedAgentIdsAsync(CancellationToken cancellationToken);
}

/// <summary>
/// Projects OpenClaw's live agent inventory into Nexus' application contract.
/// OpenClaw remains the sole source of truth: no host config or workspace path
/// is consulted by this service.
/// </summary>
public sealed class AgentService(IOpenClawControlService openClaw) : IAgentService
{
    public async Task<IReadOnlyCollection<AgentInfo>> GetAgentsAsync(
        CancellationToken cancellationToken)
    {
        var liveAgents = await openClaw.GetAgentsAsync(cancellationToken);
        var sessions = await openClaw.GetSessionsAsync(500, cancellationToken);
        var connection = openClaw.GetConnection();
        var agents = new List<AgentInfo>(liveAgents.Items.Count);

        foreach (var live in liveAgents.Items
                     .Where(item => !string.IsNullOrWhiteSpace(item.Id))
                     .DistinctBy(item => item.Id, StringComparer.OrdinalIgnoreCase))
        {
            var session = FindLatestSession(sessions.Items, live.Id);
            var description = live.Description;
            if (string.IsNullOrWhiteSpace(description) &&
                string.Equals(live.Id, "main", StringComparison.OrdinalIgnoreCase))
            {
                description = "Primary conversational agent — routing and general-purpose chat";
            }

            agents.Add(new AgentInfo(
                Id: live.Id,
                Name: string.IsNullOrWhiteSpace(live.Name) ? live.Id : live.Name,
                Role: DeriveRole(live.Id),
                Model: session?.Model ?? live.Model ?? "openclaw/default",
                Status: ResolveStatus(connection.Connected, live.Status),
                LastSeen: session?.UpdatedAt ?? connection.LastEventAt,
                Workspace: live.Workspace,
                Description: description));
        }

        return agents.AsReadOnly();
    }

    public async Task<AgentDetail?> GetAgentAsync(
        string id,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(id))
            return null;

        var liveAgents = await openClaw.GetAgentsAsync(cancellationToken);
        var live = liveAgents.Items.FirstOrDefault(item =>
            string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase));
        if (live is null)
            return null;

        var sessions = await openClaw.GetSessionsAsync(500, cancellationToken);
        var session = FindLatestSession(sessions.Items, live.Id);
        var connection = openClaw.GetConnection();
        var description = live.Description;
        if (string.IsNullOrWhiteSpace(description) &&
            string.Equals(live.Id, "main", StringComparison.OrdinalIgnoreCase))
        {
            description = "Primary conversational agent — routing and general-purpose chat";
        }

        return new AgentDetail(
            Id: live.Id,
            Name: string.IsNullOrWhiteSpace(live.Name) ? live.Id : live.Name,
            Role: DeriveRole(live.Id),
            Model: session?.Model ?? live.Model ?? "openclaw/default",
            Status: ResolveStatus(connection.Connected, live.Status),
            LastSeen: session?.UpdatedAt ?? connection.LastEventAt,
            Workspace: live.Workspace,
            AgentDir: null,
            Description: description,
            SubAgents: null,
            IdentityName: string.IsNullOrWhiteSpace(live.Name) ? live.Id : live.Name);
    }

    public async Task<IReadOnlySet<string>> GetAllowedAgentIdsAsync(
        CancellationToken cancellationToken)
    {
        var liveAgents = await openClaw.GetAgentsAsync(cancellationToken);
        return liveAgents.Items
            .Where(agent => !string.IsNullOrWhiteSpace(agent.Id))
            .Select(agent => agent.Id.Trim().ToLowerInvariant())
            .ToHashSet(StringComparer.OrdinalIgnoreCase);
    }

    private static Nexus.Api.Models.OpenClawSessionDto? FindLatestSession(
        IReadOnlyList<Nexus.Api.Models.OpenClawSessionDto> sessions,
        string agentId)
        => sessions
            .Where(item => string.Equals(item.AgentId, agentId, StringComparison.OrdinalIgnoreCase))
            .OrderByDescending(item => item.UpdatedAt)
            .FirstOrDefault();

    private static OperationalStatus ResolveStatus(bool connected, string? liveStatus)
    {
        if (!connected)
            return OperationalStatus.Offline;

        return liveStatus?.Trim().ToLowerInvariant() switch
        {
            "degraded" or "stale" or "warning" => OperationalStatus.Degraded,
            "offline" or "failed" or "error" => OperationalStatus.Offline,
            "unknown" or "unsupported" => OperationalStatus.Unknown,
            _ => OperationalStatus.Online
        };
    }

    private static string DeriveRole(string agentId) => agentId.ToLowerInvariant() switch
    {
        "iris" => "Orchestrator",
        "product-owner" => "Product Owner",
        "programmer" => "Developer",
        "programmer-fast" => "Developer",
        "reviewer" => "Reviewer",
        "architekt" => "Architect",
        "main" => "Assistant",
        _ => "Custom"
    };
}
