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

namespace Nexus.Api.Services;

public sealed class TaskService(
    ITaskRepository taskRepo,
    IActivityRepository activityRepo,
    INotificationService notificationService,
    IHttpContextAccessor httpContextAccessor) : ITaskService
{
    private static readonly HashSet<string> ValidAssignees =
        ["bao", "iris", "programmer", "reviewer", "architekt", "researcher", "executor"];

    public async Task<IReadOnlyList<WorkTask>> GetAllAsync(CancellationToken ct = default)
        => await taskRepo.GetAllAsync(ct);

    public async Task<WorkTask?> GetByIdAsync(Guid id, CancellationToken ct = default)
        => await taskRepo.GetByIdAsync(id, ct);

    public async Task<IReadOnlyList<WorkTask>> GetPendingApprovalAsync(CancellationToken ct = default)
        => await taskRepo.GetPendingApprovalAsync(ct);

    public async Task<WorkTask> CreateAsync(CreateTaskRequest request, CancellationToken ct = default)
    {
        var task = new WorkTask
        {
            Title = request.Title.Trim(),
            Priority = string.IsNullOrWhiteSpace(request.Priority) ? "Normal" : request.Priority.Trim(),
            ProjectId = request.ProjectId
        };
        await taskRepo.AddAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task {task.Title} created", TaskId = task.Id }, ct);
        return task;
    }

    public async Task<TaskOperationResult> ApproveAsync(Guid id, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        if (!TaskStateHelper.IsInProgressOrBlocked(task.State))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState, task);

        task.State = TaskStateHelper.ToStateString(TaskState.Done);
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task {task.Title} approved", TaskId = task.Id }, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> RejectAsync(Guid id, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        if (!TaskStateHelper.IsInProgressOrBlocked(task.State))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState, task);

        task.State = TaskStateHelper.ToStateString(TaskState.Backlog);
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task {task.Title} rejected, returned to backlog", TaskId = task.Id }, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> UpdateStateAsync(Guid id, string state, CancellationToken ct = default)
    {
        var canonical = TaskStateHelper.AllStates.FirstOrDefault(s => s.Equals(state, StringComparison.OrdinalIgnoreCase));
        if (canonical is null) return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        // Enforce workflow rules
        var caller = ResolveCaller();
        if (!TaskStateHelper.CanChangeState(caller, task))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        task.State = canonical;
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task {task.Title} moved to {task.State}", TaskId = task.Id }, ct);
        await CreateStatusChangeNotificationsAsync(task, canonical, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> UpdateAsync(Guid id, UpdateTaskRequest request, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        var changes = new List<string>();

        if (!string.IsNullOrWhiteSpace(request.Title) && !string.Equals(task.Title, request.Title.Trim(), StringComparison.Ordinal))
        {
            changes.Add($"Titel: \"{task.Title}\" → \"{request.Title.Trim()}\"");
            task.Title = request.Title.Trim();
        }
        if (!string.IsNullOrWhiteSpace(request.Priority) && !string.Equals(task.Priority, request.Priority.Trim(), StringComparison.OrdinalIgnoreCase))
        {
            changes.Add($"Priorität: {task.Priority} → {request.Priority.Trim()}");
            task.Priority = request.Priority.Trim();
        }
        if (request.ProjectId.HasValue)
        {
            changes.Add($"Projekt-ID geändert");
            task.ProjectId = request.ProjectId.Value == Guid.Empty ? null : request.ProjectId;
        }

        await taskRepo.UpdateAsync(task, ct);
        var changeSummary = changes.Count > 0 ? string.Join("; ", changes) : "keine sichtbaren Änderungen";
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task \"{task.Title}\" aktualisiert: {changeSummary}", TaskId = task.Id }, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> DeleteAsync(Guid id, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        if (!TaskStateHelper.IsDoneOrBacklog(task.State))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState, task);

        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task {task.Title} deleted", TaskId = task.Id }, ct);
        await taskRepo.DeleteAsync(task, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success);
    }

    // ── Dashboard-facing operations ──

    public async Task<IReadOnlyList<WorkTask>> GetOpenAsync(CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        return all.Where(t => !string.Equals(t.State, "Done", StringComparison.OrdinalIgnoreCase))
                  .OrderByDescending(t => t.CreatedAt)
                  .ToList();
    }

    /// <summary>
    /// Returns agent-tasks that are still open and where an agent is expected to respond.
    /// Iris Dashboard uses this to see who she is waiting for.
    /// </summary>
    public async Task<IReadOnlyList<WorkTask>> GetWaitingTasksAsync(CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        return all
            .Where(t => t.IsAgentTask && !string.Equals(t.State, "Done", StringComparison.OrdinalIgnoreCase))
            .OrderBy(t => t.ExpectedFrom != null ? 0 : 1)
            .ThenByDescending(t => t.UpdatedAt)
            .ToList();
    }

    /// <summary>
    /// Returns agent-tasks grouped by which agent is expected to respond,
    /// with stale-detection: tasks in InProgress/Delegated that haven't been
    /// updated within the stale threshold.
    /// </summary>
    public async Task<AgentWorkflowOverview> GetAgentWorkflowOverviewAsync(TimeSpan staleThreshold, CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        var threshold = DateTimeOffset.UtcNow - staleThreshold;

        var agentTasks = all.Where(t => t.IsAgentTask).ToList();

        var waitingForBao = agentTasks
            .Where(t => string.Equals(t.ExpectedFrom, "bao", StringComparison.OrdinalIgnoreCase) &&
                        !string.Equals(t.State, "Done", StringComparison.OrdinalIgnoreCase))
            .Select(MapToDto)
            .ToList();

        var waitingForIris = agentTasks
            .Where(t => string.Equals(t.ExpectedFrom, "iris", StringComparison.OrdinalIgnoreCase) &&
                        !string.Equals(t.State, "Done", StringComparison.OrdinalIgnoreCase))
            .Select(MapToDto)
            .ToList();

        var waitingForOthers = agentTasks
            .Where(t =>
            {
                var expected = (t.ExpectedFrom ?? "").ToLowerInvariant();
                return expected != "bao" && expected != "iris" && !string.IsNullOrWhiteSpace(expected) &&
                       !string.Equals(t.State, "Done", StringComparison.OrdinalIgnoreCase);
            })
            .Select(MapToDto)
            .ToList();

        var staleTasks = agentTasks
            .Where(t =>
                (string.Equals(t.State, "In progress", StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(t.State, "Delegated", StringComparison.OrdinalIgnoreCase)) &&
                t.UpdatedAt < threshold)
            .Select(MapToDto)
            .ToList();

        return new AgentWorkflowOverview(waitingForBao, waitingForIris, waitingForOthers,
            staleTasks, staleThreshold);
    }

    public async Task<WorkTask> CreateDashboardTaskAsync(
        string title, string? detail, string? source, string? priority,
        string? assignedTo, Guid? parentTaskId = null, CancellationToken ct = default)
    {
        // Validate parent task exists if specified
        if (parentTaskId.HasValue)
        {
            var parent = await taskRepo.GetByIdAsync(parentTaskId.Value, ct);
            if (parent is null)
                throw new ArgumentException($"Parent task {parentTaskId} not found.", nameof(parentTaskId));
        }

        var task = new WorkTask
        {
            Title = title.Trim(),
            Detail = detail?.Trim(),
            Source = string.IsNullOrWhiteSpace(source) ? "bao" : source.Trim(),
            Priority = string.IsNullOrWhiteSpace(priority) ? "Normal" : priority.Trim(),
            AssignedTo = ValidateAssignedTo(assignedTo),
            ParentTaskId = parentTaskId
        };
        await taskRepo.AddAsync(task, ct);

        var message = $"Task \"{task.Title}\" created ({task.Source})";
        if (parentTaskId.HasValue)
            message += $" [child of {parentTaskId.Value}]";
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = message, TaskId = task.Id }, ct);

        // Auto-notify: if assigned to bao, create a task_assigned notification
        if (string.Equals(assignedTo, "bao", StringComparison.OrdinalIgnoreCase))
        {
            await notificationService.CreateAsync(
                "task_assigned",
                $"Neue Aufgabe: {task.Title}",
                detail,
                "bao",
                task.Id,
                ct);
        }

        return task;
    }

    public async Task<WorkTask> CreateAgentTaskAsync(
        string title, string? detail, string? source, string? priority,
        string? assignedTo, string? expectedFrom, Guid? parentTaskId = null, CancellationToken ct = default)
    {
        var task = await CreateDashboardTaskAsync(title, detail, source, priority, assignedTo, parentTaskId, ct);

        task.IsAgentTask = true;
        task.ExpectedFrom = string.IsNullOrWhiteSpace(expectedFrom) ? null : expectedFrom.Trim().ToLowerInvariant();

        // Persist the agent-task-specific fields
        await taskRepo.UpdateAsync(task, ct);

        await activityRepo.AddAsync(new ActivityEvent
        {
            Type = "agent_task",
            Message = $"Agent-Task created: \"{task.Title}\" (Source: {task.Source}, Expected: {task.ExpectedFrom ?? "none"})",
            TaskId = task.Id
        }, ct);

        // Notify iris about new agent-task
        await notificationService.CreateAsync(
            "agent_task_created",
            $"Neuer Agent-Task: {task.Title}",
            detail,
            "iris",
            task.Id,
            ct);

        return task;
    }

    public async Task<TaskOperationResult> UpdateDashboardTaskAsync(
        Guid id, string? title, string? detail, string? source,
        string? priority, string? assignedTo, DateTimeOffset? dueDate = null, CancellationToken ct = default)
    {
        var caller = ResolveCaller();
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        var changes = new List<string>();

        if (!string.IsNullOrWhiteSpace(title) && !string.Equals(task.Title, title.Trim(), StringComparison.Ordinal))
        {
            changes.Add($"Titel: \"{task.Title}\" → \"{title.Trim()}\"");
            task.Title = title.Trim();
        }
        if (detail is not null)
        {
            var newDetail = string.IsNullOrWhiteSpace(detail) ? null : detail.Trim();
            if (!string.Equals(task.Detail ?? "", newDetail ?? "", StringComparison.Ordinal))
            {
                changes.Add("Beschreibung aktualisiert");
                task.Detail = newDetail;
            }
        }
        if (!string.IsNullOrWhiteSpace(source))
            task.Source = source.Trim();
        if (!string.IsNullOrWhiteSpace(priority) && !string.Equals(task.Priority, priority.Trim(), StringComparison.OrdinalIgnoreCase))
        {
            changes.Add($"Priorität: {task.Priority} → {priority.Trim()}");
            task.Priority = priority.Trim();
        }
        if (assignedTo is not null)
        {
            var validated = ValidateAssignedTo(assignedTo);
            if (!string.Equals(task.AssignedTo ?? "", validated ?? "", StringComparison.OrdinalIgnoreCase))
            {
                changes.Add($"Zuständig: {task.AssignedTo ?? "niemand"} → {validated ?? "niemand"}");
                task.AssignedTo = validated;
            }
        }
        if (dueDate.HasValue)
        {
            if (task.DueDate?.Date != dueDate.Value.Date)
            {
                changes.Add($"Fällig: {task.DueDate?.ToString("yyyy-MM-dd") ?? "kein Datum"} → {dueDate.Value:yyyy-MM-dd}");
                task.DueDate = dueDate;
            }
        }

        await taskRepo.UpdateAsync(task, ct);

        var changeSummary = changes.Count > 0 ? string.Join("; ", changes) : "keine sichtbaren Änderungen";
        await activityRepo.AddAsync(new ActivityEvent
        {
            Type = "task",
            Message = $"Task \"{task.Title}\" aktualisiert von {caller}: {changeSummary}",
            TaskId = task.Id
        }, ct);

        // Notification: wenn Bao die Task geändert hat, Iris benachrichtigen
        if (changes.Count > 0 && caller == "bao")
        {
            await notificationService.CreateAsync(
                "task_content_changed",
                $"Bao hat \"{task.Title}\" geändert",
                $"{changeSummary}",
                "iris",
                task.Id,
                ct);
        }

        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> UpdateStatusAsync(Guid id, string status, CancellationToken ct = default)
    {
        if (!TaskStateHelper.IsValidState(status))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        // Enforce workflow rules
        var caller = ResolveCaller();
        if (!TaskStateHelper.CanChangeState(caller, task))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        var canonical = TaskStateHelper.AllStates.First(s => s.Equals(status, StringComparison.OrdinalIgnoreCase));
        task.State = canonical;
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task \"{task.Title}\" → {canonical}", TaskId = task.Id }, ct);
        await CreateStatusChangeNotificationsAsync(task, canonical, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> CompleteViaQueueAsync(Guid id, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        task.State = "Done";
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task \"{task.Title}\" completed via queue", TaskId = task.Id }, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public async Task<TaskOperationResult> CyclePriorityAsync(Guid id, CancellationToken ct = default)
    {
        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        task.Priority = task.Priority.ToLowerInvariant() switch
        {
            "high" => "Medium",
            "medium" => "Low",
            "low" => "High",
            _ => "Medium"
        };

        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task \"{task.Title}\" priority → {task.Priority}", TaskId = task.Id }, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    // ── Board operations ──

    public async Task<BoardResponse> GetBoardAsync(CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        var offen = new List<DashboardTaskDto>();
        var inProgress = new List<DashboardTaskDto>();
        var delegated = new List<DashboardTaskDto>();
        var review = new List<DashboardTaskDto>();
        var blocked = new List<DashboardTaskDto>();
        var done = new List<DashboardTaskDto>();

        foreach (var task in all)
        {
            var dto = MapToDto(task);
            switch (task.State.ToLowerInvariant())
            {
                case "backlog":
                    offen.Add(dto); break;
                case "in progress":
                    inProgress.Add(dto); break;
                case "delegated":
                    delegated.Add(dto); break;
                case "review":
                    review.Add(dto); break;
                case "blocked":
                    blocked.Add(dto); break;
                case "done":
                    done.Add(dto); break;
                default:
                    offen.Add(dto); break;
            }
        }

        offen.Sort(SortByPriorityThenCreatedAt);
        inProgress.Sort(SortByPriorityThenCreatedAt);
        delegated.Sort(SortByPriorityThenCreatedAt);
        review.Sort(SortByPriorityThenCreatedAt);
        blocked.Sort(SortByPriorityThenCreatedAt);
        done.Sort(SortByPriorityThenCreatedAt);

        return new BoardResponse(offen, inProgress, delegated, review, blocked, done);
    }

    private static int SortByPriorityThenCreatedAt(DashboardTaskDto a, DashboardTaskDto b)
    {
        var priorityCompare = PriorityScore(b.Priority).CompareTo(PriorityScore(a.Priority));
        return priorityCompare != 0 ? priorityCompare : a.CreatedAt.CompareTo(b.CreatedAt);
    }

    private static int PriorityScore(string priority) => priority.ToLowerInvariant() switch
    {
        "high" => 3,
        "medium" => 2,
        "normal" => 2,
        "low" => 1,
        _ => 2
    };

    public async Task<TaskOperationResult> MoveTaskAsync(Guid id, string newState, CancellationToken ct = default)
    {
        // Resolve canonical state: accept board group keys or canonical strings
        var canonical = TaskStateHelper.AllStates
            .FirstOrDefault(s => s.Equals(newState, StringComparison.OrdinalIgnoreCase));

        if (canonical is null)
        {
            // Try mapping from board group key
            canonical = TaskStateHelper.BoardGroupToState(newState);
        }

        if (canonical is null)
            return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        var task = await taskRepo.GetByIdAsync(id, ct);
        if (task is null) return new TaskOperationResult(TaskOperationOutcome.NotFound);

        // Enforce workflow rules
        var caller = ResolveCaller();
        if (!TaskStateHelper.CanChangeState(caller, task))
            return new TaskOperationResult(TaskOperationOutcome.InvalidState);

        task.State = canonical;
        await taskRepo.UpdateAsync(task, ct);
        await activityRepo.AddAsync(new ActivityEvent { Type = "task", Message = $"Task \"{task.Title}\" moved to {canonical}", TaskId = task.Id }, ct);
        await CreateStatusChangeNotificationsAsync(task, canonical, ct);
        return new TaskOperationResult(TaskOperationOutcome.Success, task);
    }

    public Task<int> ResetStaleAsync(int staleHours, CancellationToken ct = default)
    {
        var normalizedHours = Math.Max(1, staleHours);
        return ResetStaleInProgressTasksAsync(TimeSpan.FromHours(normalizedHours), ct);
    }

    public async Task<int> ResetStaleInProgressTasksAsync(TimeSpan staleThreshold, CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        var threshold = DateTimeOffset.UtcNow - staleThreshold;
        var staleTasks = all.Where(t =>
            (string.Equals(t.State, "In progress", StringComparison.OrdinalIgnoreCase) ||
             string.Equals(t.State, "Delegated", StringComparison.OrdinalIgnoreCase)) &&
            t.UpdatedAt < threshold).ToList();

        foreach (var task in staleTasks)
        {
            var prevState = task.State;
            task.State = "Backlog";
            await taskRepo.UpdateAsync(task, ct);
            await activityRepo.AddAsync(new ActivityEvent
            {
                Type = "task",
                Message = $"Task \"{task.Title}\" reset from {prevState} to Backlog (stale)",
                TaskId = task.Id
            }, ct);
        }

        return staleTasks.Count;
    }

    public async Task<IReadOnlyList<WorkTask>> GetChildTasksAsync(Guid parentId, CancellationToken ct = default)
    {
        var all = await taskRepo.GetAllAsync(ct);
        return all.Where(t => t.ParentTaskId == parentId)
                  .OrderByDescending(t => t.CreatedAt)
                  .ToList();
    }

    public async Task<List<ActivityEvent>> GetTaskActivityAsync(Guid taskId, CancellationToken ct = default)
    {
        var all = await activityRepo.GetRecentAsync(100, ct);
        return all.Where(e => e.TaskId == taskId).ToList();
    }

    private static DashboardTaskDto MapToDto(WorkTask t) => new(
        t.Id, t.Title, t.Detail, t.Source, t.State, t.Priority, t.AssignedTo,
        t.ParentTaskId, t.DueDate, t.CreatedAt, t.UpdatedAt,
        t.IsAgentTask, t.ExpectedFrom);

    /// <summary>
    /// Validates AssignedTo — only recognized agent values are accepted.
    /// Returns null for invalid values.
    /// </summary>
    private static string? ValidateAssignedTo(string? assignedTo)
    {
        if (string.IsNullOrWhiteSpace(assignedTo)) return null;
        var lower = assignedTo.Trim().ToLowerInvariant();
        return ValidAssignees.Contains(lower) ? lower : null;
    }

    /// <summary>
    /// Resolves the caller identity from the HTTP context.
    /// Reads the X-Agent-Id header for agent calls, falls back to JWT name.
    /// Outside HTTP context → "nexus-system" (allowed for internal Cron/ResetStale ops).
    /// </summary>
    private string ResolveCaller()
    {
        var httpContext = httpContextAccessor.HttpContext;
        if (httpContext is null) return "nexus-system"; // internal system ops allowed

        var agentHeader = httpContext.Request.Headers["X-Agent-Id"].FirstOrDefault();
        if (!string.IsNullOrWhiteSpace(agentHeader))
            return agentHeader.Trim().ToLowerInvariant();

        var user = httpContext.User;
        var nameClaim = user?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
        return nameClaim?.ToLowerInvariant() ?? "";
    }

    /// <summary>
    /// Creates status-change notifications when a task moves to a new state.
    /// - Wenn Bao ändert → Iris benachrichtigen
    /// - Wenn Iris ändert → Bao benachrichtigen
    /// - Review/Blocked bekommen spezifische Töne
    /// </summary>
    private async Task CreateStatusChangeNotificationsAsync(WorkTask task, string canonical, CancellationToken ct)
    {
        var caller = ResolveCaller();

        if (string.Equals(canonical, "Review", StringComparison.OrdinalIgnoreCase))
        {
            await notificationService.CreateAsync(
                "task_review",
                $"Task zur Überprüfung: {task.Title}",
                $"Status auf Review geändert von {caller}",
                "bao",
                task.Id,
                ct);
        }
        else if (string.Equals(canonical, "Blocked", StringComparison.OrdinalIgnoreCase))
        {
            await notificationService.CreateAsync(
                "task_blocked",
                $"Aufgabe blockiert: {task.Title}",
                $"Die Task wurde von {caller} auf Blockiert gesetzt.",
                "iris",
                task.Id,
                ct);
        }
        else
        {
            // Allgemeine Statusänderung: Gegenüber benachrichtigen
            if (caller == "bao")
            {
                await notificationService.CreateAsync(
                    "task_status_changed",
                    $"Bao hat Status geändert: {task.Title}",
                    $"Status → {canonical}",
                    "iris",
                    task.Id,
                    ct);
            }
            else if (caller == "iris")
            {
                await notificationService.CreateAsync(
                    "task_status_changed",
                    $"Iris hat Status geändert: {task.Title}",
                    $"Status → {canonical}",
                    "bao",
                    task.Id,
                    ct);
            }
        }
    }
}
