using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Repositories;

namespace Nexus.Api.Services;

public sealed class StaleTaskRecoveryService(
    ITaskRepository taskRepository,
    IActivityRepository activityRepository,
    ILiveUpdateService liveUpdateService) : IStaleTaskRecoveryService
{
    public async Task<int> ResetStaleInProgressTasksAsync(TimeSpan staleThreshold, CancellationToken ct = default)
    {
        var threshold = DateTimeOffset.UtcNow - staleThreshold;
        var staleTasks = await GetStaleTasksAsync(threshold, ct);
        if (staleTasks.Count == 0)
            return 0;

        var latestActivityByTaskId = await GetLatestActivityByTaskIdAsync(staleTasks.Select(task => task.Id), ct);
        var now = DateTimeOffset.UtcNow;
        var resetCount = 0;
        var resetTaskIds = new List<Guid>();

        foreach (var task in staleTasks)
        {
            var currentTask = await taskRepository.GetByIdAsync(task.Id, ct);
            if (currentTask is null || !IsStaleInProgress(currentTask, threshold))
                continue;

            latestActivityByTaskId.TryGetValue(currentTask.Id, out var lastActivityAt);
            var message = BuildActivityMessage(currentTask, staleThreshold, now, lastActivityAt);
            var updated = await taskRepository.TryResetStaleInProgressToBacklogAsync(
                currentTask.Id,
                threshold,
                now,
                ct);
            if (!updated)
                continue;

            await activityRepository.AddAsync(new ActivityEvent
            {
                Type = "task",
                Message = message,
                TaskId = task.Id
            }, ct);

            resetCount++;
            resetTaskIds.Add(task.Id);
        }

        if (resetCount > 0)
            liveUpdateService.Publish(
                "tasks.board.snapshot",
                new { taskIds = resetTaskIds },
                "board");

        return resetCount;
    }

    private async Task<List<WorkTask>> GetStaleTasksAsync(DateTimeOffset threshold, CancellationToken ct)
    {
        var allTasks = await taskRepository.GetAllAsync(ct);

        return allTasks
            .Where(task => IsStaleInProgress(task, threshold))
            .ToList();
    }

    private static bool IsStaleInProgress(WorkTask task, DateTimeOffset threshold)
        => string.Equals(task.State, TaskStateHelper.ToStateString(TaskState.InProgress), StringComparison.OrdinalIgnoreCase)
           && task.UpdatedAt < threshold;

    private async Task<Dictionary<Guid, DateTimeOffset>> GetLatestActivityByTaskIdAsync(
        IEnumerable<Guid> taskIds,
        CancellationToken ct)
    {
        var activities = await activityRepository.GetRecentForTasksAsync(taskIds, ct);

        return activities
            .Where(activity => activity.TaskId.HasValue)
            .GroupBy(activity => activity.TaskId!.Value)
            .ToDictionary(group => group.Key, group => group.Max(activity => activity.CreatedAt));
    }

    private static string BuildActivityMessage(
        WorkTask task,
        TimeSpan staleThreshold,
        DateTimeOffset now,
        DateTimeOffset? lastActivityAt)
    {
        var staleAge = now - task.UpdatedAt;
        var details = new List<string>
        {
            "reason=stale-recovery",
            "previous status In progress",
            $"stale reference {now:O}",
            $"stale age {FormatDuration(staleAge)}",
            $"threshold {FormatDuration(staleThreshold)}"
        };

        if (lastActivityAt.HasValue)
            details.Add($"last activity {lastActivityAt.Value:O}");

        details.Add($"last update {task.UpdatedAt:O}");
        details.Add("new status Backlog");

        return $"Task \"{task.Title}\" reset from In progress to Backlog by stale recovery ({string.Join("; ", details)})";
    }

    private static string FormatDuration(TimeSpan duration)
        => duration.ToString(@"dd\.hh\:mm\:ss");

}
