using Nexus.Api.Models;

namespace Nexus.Api.Services;

public sealed class DashboardService(
    IOpenClawGatewayClient gateway,
    IOpenClawControlService openClaw,
    IOpenClawChatService chat,
    ITaskService taskService,
    ILogger<DashboardService> logger) : IDashboardService
{
    public async Task<DashboardStatus> GetStatusAsync()
    {
        var connection = openClaw.GetConnection();
        var tasks = await openClaw.GetTasksAsync(200);
        var sessions = await openClaw.GetSessionsAsync(200);
        return new DashboardStatus(
            connection.Connected,
            connection.Connected ? "Online" : "Offline",
            sessions.Items.Count(session => session.Status is "running" or "active"),
            tasks.Items.Count(task => task.Status is "queued" or "running"));
    }

    public async Task<List<DashboardAgentInfo>> GetAgentsAsync()
    {
        var agentsTask = openClaw.GetAgentsAsync();
        var sessionsTask = openClaw.GetSessionsAsync(500);
        var tasksTask = openClaw.GetTasksAsync(500);
        await Task.WhenAll(agentsTask, sessionsTask, tasksTask);
        var agents = agentsTask.Result;
        var sessions = sessionsTask.Result;
        var tasks = tasksTask.Result;

        return agents.Items.Select(agent =>
        {
            var session = sessions.Items
                .Where(item => string.Equals(item.AgentId, agent.Id, StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(item => item.UpdatedAt)
                .FirstOrDefault();
            var task = tasks.Items
                .Where(item =>
                    string.Equals(item.AgentId, agent.Id, StringComparison.OrdinalIgnoreCase) ||
                    (!string.IsNullOrWhiteSpace(item.SessionKey) &&
                     string.Equals(item.SessionKey, session?.Key, StringComparison.Ordinal)))
                .Where(item => item.Status is "queued" or "running" or "active")
                .OrderByDescending(item => item.UpdatedAt ?? item.StartedAt ?? item.CreatedAt)
                .FirstOrDefault();
            var active = session?.Status is "running" or "active";
            return new DashboardAgentInfo(
                Id: agent.Id,
                Name: agent.Name,
                Role: DeriveRole(agent.Id),
                Model: session?.Model ?? agent.Model ?? "openclaw/default",
                IsActive: active,
                CurrentTask: task?.Title ?? (active ? session?.Title : null),
                Description: agent.Description,
                Tags: BuildAgentTags(agent.Id),
                Progress: task?.Progress,
                Workload: sessions.Items.Count(item =>
                    string.Equals(item.AgentId, agent.Id, StringComparison.OrdinalIgnoreCase) &&
                    item.Status is "running" or "active"),
                Goal: null,
                RoleBadge: DeriveRoleBadge(agent.Id),
                StatusLabel: active ? "Working" : "Ready",
                StatusKind: active ? "working" : "ready",
                StatusDetail: session is null ? "No Gateway session reported." : session.Title,
                Elapsed: null,
                Think: null,
                Next: null,
                TotalTokens: session?.TotalTokens,
                CostUsd: null,
                TelemetryAt: session?.UpdatedAt);
        }).ToList();
    }

    public async Task<List<FeedEntry>> GetOperationsAsync(int limit, string? agentFilter)
    {
        var activity = await openClaw.GetActivityAsync(Math.Clamp(limit, 1, 100));
        var entries = activity.Items;
        if (!string.IsNullOrWhiteSpace(agentFilter))
            entries = entries
                .Where(item => string.Equals(item.AgentId, agentFilter, StringComparison.OrdinalIgnoreCase))
                .ToArray();

        return entries.Select(item => new FeedEntry(
            item.AgentId ?? item.Actor ?? "OpenClaw",
            item.Message,
            item.OccurredAt?.ToString("O") ?? activity.CheckedAt.ToString("O"),
            item.OccurredAt?.ToLocalTime().ToString("HH:mm") ?? "--:--",
            item.AgentId,
            item.EventType)).ToList();
    }

    public async Task<ChatResponse> SendChatAsync(string agentId, string message)
    {
        try
        {
            var context = OpenClawInvocationContextFactory.Create(
                actor: "nexus-dashboard",
                idempotencyKey: null,
                correlationId: null,
                traceParent: null);
            var result = await chat.SendAsync(
                message,
                $"nexus-dashboard-{agentId.ToLowerInvariant()}",
                agentId,
                new OpenClawInvocationMetadata(
                    context.IdempotencyKey,
                    context.CorrelationId,
                    context.Actor,
                    context.TraceParent),
                CancellationToken.None);
            return new ChatResponse(true, result.Content, null);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Dashboard chat send failed");
            return new ChatResponse(false, null, "OpenClaw chat endpoint is not enabled or reachable.");
        }
    }

    public async Task<List<MessageEntry>> GetMessagesAsync(string? sessionKey, int limit, int offset)
    {
        try
        {
            var key = string.IsNullOrWhiteSpace(sessionKey) ? "agent:iris:main" : sessionKey.Trim();
            var messages = await gateway.GetSessionHistoryAsync(key, Math.Clamp(limit, 1, 200), Math.Max(0, offset));
            return messages
                .Where(m => string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase)
                         || string.Equals(m.Role, "assistant", StringComparison.OrdinalIgnoreCase))
                .ToList();
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Dashboard messages fetch failed");
            return [];
        }
    }

    public async Task<List<QueueItem>> GetQueueAsync(CancellationToken ct)
    {
        try
        {
            var cronTask = openClaw.GetCronJobsAsync(200, ct);
            var tasksTask = taskService.GetOpenAsync(ct);
            await Task.WhenAll(cronTask, tasksTask);

            var merged = cronTask.Result.Items.Select(job => new QueueItem(
                job.Id,
                job.Name,
                job.Status,
                "medium",
                "cron",
                FormatWaitTime(job.NextRunAt))).ToList();
            foreach (var t in tasksTask.Result)
            {
                merged.Add(new QueueItem("task-" + t.Id, t.Title, t.State, NormalizePriority(t.Priority), "task", "--"));
            }

            return merged
                .OrderBy(q => PriorityOrder.GetValueOrDefault(q.Priority, 99))
                .ToList();
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Dashboard queue fetch failed");
            return [];
        }
    }

    public async Task<GatewayRuntimeInfo> GetGatewayInfoAsync(CancellationToken ct)
    {
        await Task.CompletedTask;
        var connection = openClaw.GetConnection();
        var versionStatus = !connection.Connected
            ? "error"
            : !connection.VersionPinned
                ? "unpinned"
                : connection.GatewayVersion is null
                    ? "missing"
                    : connection.VersionMatches ? "matched" : "drift";
        return new GatewayRuntimeInfo(
            connection.Connected,
            connection.Endpoint,
            connection.GatewayVersion,
            connection.RequiredVersion,
            connection.VersionPinned,
            connection.VersionMatches,
            versionStatus,
            connection.CheckedAt,
            connection.Message,
            connection.Recovery);
    }

    public async Task<QueueDeleteResult> DeleteQueueItemAsync(string id, string? source, CancellationToken ct)
    {
        if (string.Equals(source, "cron", StringComparison.OrdinalIgnoreCase))
            return new QueueDeleteResult(QueueDeleteOutcome.Ignored);

        if (string.Equals(source, "task", StringComparison.OrdinalIgnoreCase) || id.StartsWith("task-"))
        {
            if (!id.StartsWith("task-")) return new QueueDeleteResult(QueueDeleteOutcome.InvalidTaskId);
            if (!Guid.TryParse(id["task-".Length..], out var guid))
                return new QueueDeleteResult(QueueDeleteOutcome.InvalidTaskId);

            var result = await taskService.CompleteViaQueueAsync(guid, ct);
            return result.Outcome switch
            {
                TaskOperationOutcome.NotFound => new QueueDeleteResult(QueueDeleteOutcome.TaskNotFound),
                _ => new QueueDeleteResult(QueueDeleteOutcome.Deleted)
            };
        }

        return new QueueDeleteResult(QueueDeleteOutcome.NotFound);
    }

    public async Task<QueuePriorityResult> CycleQueuePriorityAsync(string id, CancellationToken ct)
    {
        if (!id.StartsWith("task-"))
            return new QueuePriorityResult(QueuePriorityOutcome.Ignored);

        if (!Guid.TryParse(id["task-".Length..], out var guid))
            return new QueuePriorityResult(QueuePriorityOutcome.InvalidTaskId);

        var result = await taskService.CyclePriorityAsync(guid, ct);
        return result.Outcome switch
        {
            TaskOperationOutcome.NotFound => new QueuePriorityResult(QueuePriorityOutcome.TaskNotFound),
            _ => new QueuePriorityResult(QueuePriorityOutcome.Updated, result.Task?.Priority)
        };
    }

    public async Task<AgentModelInfo?> GetAgentModelAsync(string agentId)
    {
        var sessions = await openClaw.GetSessionsAsync(500);
        var session = sessions.Items
            .Where(item => string.Equals(item.AgentId, agentId, StringComparison.OrdinalIgnoreCase))
            .OrderByDescending(item => item.UpdatedAt)
            .FirstOrDefault();
        return session?.Model is null
            ? null
            : new AgentModelInfo(session.Model, session.Provider ?? "unknown");
    }

    public async Task<bool> SetAgentModelAsync(string agentId, string model)
    {
        var result = await openClaw.PatchSessionModelAsync(
            $"agent:{agentId}:main",
            model);
        return result.Ok;
    }

    public async Task<List<AgentActivityEntry>> GetAgentActivityAsync(string agentId, int limit)
    {
        var response = await openClaw.GetActivityAsync(Math.Clamp(limit * 5, 1, 100));
        return response.Items
            .Where(item => string.Equals(item.AgentId, agentId, StringComparison.OrdinalIgnoreCase))
            .Take(Math.Clamp(limit, 1, 20))
            .Select(item => new AgentActivityEntry(
                FormatTimeAgo(item.OccurredAt),
                item.Message,
                item.OccurredAt ?? response.CheckedAt,
                item.Source))
            .ToList();
    }

    public async Task<List<ModelOption>> GetAvailableModelsAsync(CancellationToken ct)
    {
        var models = await openClaw.GetModelsAsync(ct);
        return models.Items
            .Where(model => !string.IsNullOrWhiteSpace(model.Id))
            .Select(model => new ModelOption(
                model.Id,
                string.IsNullOrWhiteSpace(model.Name) ? model.Id : model.Name,
                model.Provider))
            .DistinctBy(model => model.Id, StringComparer.OrdinalIgnoreCase)
            .OrderBy(model => model.Provider, StringComparer.OrdinalIgnoreCase)
            .ThenBy(model => model.Name, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    private static string NormalizePriority(string priority) => priority.ToLowerInvariant() switch
    {
        "high" or "critical" or "urgent" => "high",
        "low" or "minor" => "low",
        _ => "medium"
    };

    private static readonly Dictionary<string, int> PriorityOrder = new(StringComparer.OrdinalIgnoreCase)
    {
        ["high"] = 0, ["medium"] = 1, ["low"] = 2
    };

    private static string DeriveRole(string agentId) => agentId.ToLowerInvariant() switch
    {
        "iris" => "Orchestrator",
        "programmer" => "Developer",
        "reviewer" => "Reviewer",
        "architekt" => "Architect",
        "researcher" => "Researcher",
        "executor" => "Executor",
        "main" => "Assistant",
        _ => "Agent"
    };

    private static string DeriveRoleBadge(string agentId) => agentId.ToLowerInvariant() switch
    {
        "iris" => "badge-violet",
        "reviewer" => "badge-amber",
        "executor" => "badge-green",
        _ => "badge-blue"
    };

    private static string[] BuildAgentTags(string agentId) => agentId.ToLowerInvariant() switch
    {
        "iris" => ["orchestration", "delegation", "approvals"],
        "programmer" => ["code", "build", "test"],
        "reviewer" => ["review", "quality", "security"],
        "architekt" => ["architecture", "infrastructure"],
        "researcher" => ["research", "analysis"],
        "executor" => ["execution", "operations"],
        _ => ["openclaw"]
    };

    private static string FormatWaitTime(DateTimeOffset? nextRunAt)
    {
        if (nextRunAt is null)
            return "--";
        var remaining = nextRunAt.Value - DateTimeOffset.UtcNow;
        if (remaining <= TimeSpan.Zero) return "now";
        if (remaining.TotalMinutes < 1) return "<1m";
        if (remaining.TotalHours < 1) return $"{(int)remaining.TotalMinutes}m";
        if (remaining.TotalDays < 1) return $"{(int)remaining.TotalHours}h";
        return $"{(int)remaining.TotalDays}d";
    }

    private static string FormatTimeAgo(DateTimeOffset? timestamp)
    {
        if (timestamp is null)
            return "unknown";
        var elapsed = DateTimeOffset.UtcNow - timestamp.Value;
        if (elapsed.TotalMinutes < 1) return "now";
        if (elapsed.TotalHours < 1) return $"{(int)elapsed.TotalMinutes}m";
        if (elapsed.TotalDays < 1) return $"{(int)elapsed.TotalHours}h";
        return $"{(int)elapsed.TotalDays}d";
    }
}
