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

namespace Nexus.Api.Services;

/// <summary>
/// Concrete implementation of ITaskBridgeService.
/// Wraps ITaskService, IActivityRepository, INotificationService, and ILiveUpdateService
/// into structured, predictable commands for agent-facing usage.
///
/// All operations produce typed TaskBridgeResult<T> with explicit error codes,
/// making agent consumption safe and debuggable.
/// </summary>
public sealed class TaskBridgeService(
    ITaskService taskService,
    IAgentService agentService,
    IActivityRepository activityRepo,
    INotificationService notificationService,
    ILiveUpdateService liveUpdateService) : ITaskBridgeService
{
    private static readonly HashSet<string> ValidStates =
        new(TaskStateHelper.AllStates, StringComparer.OrdinalIgnoreCase);

    // ──────────────────────────────── Create Task ────────────────────────────────

    public async Task<TaskBridgeResult<DashboardTaskDto>> CreateTaskAsync(
        string title,
        string? detail = null,
        string? source = "iris",
        string? priority = "Normal",
        string? assignedTo = null,
        Guid? projectId = null,
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(title))
            return Error<DashboardTaskDto>(TaskBridgeOutcome.ValidationError, "Title is required.");

        var normalizedSource = NormalizeSource(source);

        var task = await taskService.CreateDashboardTaskAsync(
            title.Trim(), detail?.Trim(), normalizedSource, priority, assignedTo, parentTaskId: null, ct);

        var dto = await taskService.GetDashboardTaskByIdAsync(task.Id, ct) ?? MapToDto(task);
        return Success(dto);
    }

    // ──────────────────────────────── Create Child Task ──────────────────────────

    public async Task<TaskBridgeResult<DashboardTaskDto>> CreateChildTaskAsync(
        Guid parentTaskId,
        string title,
        string? detail = null,
        string? source = "iris",
        string? priority = "Normal",
        string? assignedTo = null,
        string? expectedFrom = null,
        bool startsInProgress = false,
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(title))
            return Error<DashboardTaskDto>(TaskBridgeOutcome.ValidationError, "Title is required.");

        // Verify parent exists
        var parent = await taskService.GetByIdAsync(parentTaskId, ct);
        if (parent is null)
            return Error<DashboardTaskDto>(TaskBridgeOutcome.NotFound, $"Parent task {parentTaskId} not found.");

        var task = await taskService.CreateAgentTaskAsync(
            title.Trim(), detail?.Trim(), NormalizeSource(source),
            priority, assignedTo, expectedFrom, parentTaskId, startsInProgress, null, ct);

        // If parent was in Backlog, move it to InProgress (coordination starts)
        if (string.Equals(parent.State, "Backlog", StringComparison.OrdinalIgnoreCase))
        {
            var parentTransition = await taskService.StartCoordinationAsync(parentTaskId, ct);
            if (parentTransition.Outcome != TaskOperationOutcome.Success)
            {
                return Error<DashboardTaskDto>(
                    TaskBridgeOutcome.InvalidState,
                    $"Parent task {parentTaskId} could not be moved to In progress for coordination.");
            }
        }

        var dto = await taskService.GetDashboardTaskByIdAsync(task.Id, ct) ?? MapToDto(task);
        return Success(dto);
    }

    // ──────────────────────────────── Update Status ──────────────────────────────

    public async Task<TaskBridgeResult<DashboardTaskDto>> UpdateStatusAsync(
        Guid taskId,
        string state,
        string? callerAgent = null,
        CancellationToken ct = default)
    {
        if (!ValidStates.Contains(state))
            return Error<DashboardTaskDto>(TaskBridgeOutcome.ValidationError,
                $"Invalid state '{state}'. Valid: {string.Join(", ", TaskStateHelper.AllStates)}");

        var task = await taskService.GetByIdAsync(taskId, ct);
        if (task is null)
            return Error<DashboardTaskDto>(TaskBridgeOutcome.NotFound, $"Task {taskId} not found.");

        // Check authorization
        if (!TaskStateHelper.CanChangeState(callerAgent, task))
            return Error<DashboardTaskDto>(TaskBridgeOutcome.Unauthorized,
                $"Agent '{callerAgent}' is not authorized to change task state. Only iris and bao may move tasks.");

        var result = await taskService.UpdateStatusAsync(taskId, state, ct);
        if (result.Outcome != TaskOperationOutcome.Success)
            return Error<DashboardTaskDto>(TaskBridgeOutcome.InvalidState, "Status update rejected.");

        var dto = await taskService.GetDashboardTaskByIdAsync(result.Task!.Id, ct) ?? MapToDto(result.Task);
        return Success(dto);
    }

    // ──────────────────────────────── Append Activity ────────────────────────────

    public async Task<TaskBridgeResult<ActivityEvent>> AppendActivityAsync(
        Guid taskId,
        string message,
        string? type = "comment",
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(message))
            return Error<ActivityEvent>(TaskBridgeOutcome.ValidationError, "Message is required.");

        var task = await taskService.GetByIdAsync(taskId, ct);
        if (task is null)
            return Error<ActivityEvent>(TaskBridgeOutcome.NotFound, $"Task {taskId} not found.");

        var ev = new ActivityEvent
        {
            Type = type ?? "comment",
            Message = message.Trim(),
            TaskId = taskId
        };

        await activityRepo.AddAsync(ev, ct);

        // Keep the legacy dashboard stream as an invalidation adapter without
        // rebuilding every task and activity row in this mutation request.
        liveUpdateService.Publish(
            "tasks.board.snapshot",
            new { taskId },
            "board");

        return Success(ev);
    }

    // ──────────────────────────────── Handoff ────────────────────────────────────

    public async Task<TaskBridgeResult<DashboardTaskDto>> HandoffAsync(
        Guid taskId,
        string targetAgent,
        string? note = null,
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(targetAgent))
            return Error<DashboardTaskDto>(TaskBridgeOutcome.ValidationError, "Target agent is required.");

        var task = await taskService.GetByIdAsync(taskId, ct);
        if (task is null)
            return Error<DashboardTaskDto>(TaskBridgeOutcome.NotFound, $"Task {taskId} not found.");

        var normalizedTarget = await NormalizeActorAsync(targetAgent, ct);
        if (normalizedTarget is null)
            return Error<DashboardTaskDto>(TaskBridgeOutcome.ValidationError, $"Unknown target agent '{targetAgent}'.");

        var handoffNote = string.IsNullOrWhiteSpace(note)
            ? $"Handoff → {normalizedTarget}"
            : $"Handoff → {normalizedTarget}: {note.Trim()}";

        // Update expected-from and optionally assigned-to
        task.ExpectedFrom = normalizedTarget;

        // If this is a child task (has parent), keep assigned-to on the child
        // If standalone, set assigned-to to the target
        if (!task.ParentTaskId.HasValue)
            task.AssignedTo = normalizedTarget;

        await taskService.UpdateDashboardTaskAsync(
            taskId, title: null, detail: null, source: null,
            priority: null, assignedTo: task.AssignedTo, dueDate: null, ct);

        // Append handoff activity
        await AppendActivityAsync(taskId, handoffNote, "handoff", ct);

        // Notify the target
        await notificationService.CreateAsync(
            "task_assigned",
            $"Handoff: {task.Title}",
            handoffNote,
            normalizedTarget,
            task.Id,
            ct);

        var dto = await taskService.GetDashboardTaskByIdAsync(task.Id, ct) ?? MapToDto(task);
        return Success(dto);
    }

    // ──────────────────────────────── Query ──────────────────────────────────────

    public async Task<BoardResponse> GetBoardAsync(CancellationToken ct = default)
        => await taskService.GetBoardAsync(ct);

    public async Task<TaskBridgeResult<DashboardTaskDto>> GetTaskAsync(
        Guid taskId, CancellationToken ct = default)
    {
        var dto = await taskService.GetDashboardTaskByIdAsync(taskId, ct);
        return dto is null
            ? Error<DashboardTaskDto>(TaskBridgeOutcome.NotFound, $"Task {taskId} not found.")
            : Success(dto);
    }

    public async Task<IReadOnlyList<DashboardTaskDto>> GetChildTasksAsync(
        Guid parentTaskId, CancellationToken ct = default)
    {
        var board = await taskService.GetBoardAsync(ct);
        return FlattenBoard(board)
            .Where(task => task.ParentTaskId == parentTaskId)
            .OrderByDescending(task => task.UpdatedAt)
            .ToList();
    }

    public async Task<List<ActivityEvent>> GetTaskActivityAsync(
        Guid taskId, CancellationToken ct = default)
        => await taskService.GetTaskActivityAsync(taskId, ct);

    public async Task<AgentWorkflowOverview> GetAgentOverviewAsync(
        TimeSpan? staleThreshold = null, CancellationToken ct = default)
    {
        var threshold = staleThreshold ?? TimeSpan.FromHours(2);
        return await taskService.GetAgentWorkflowOverviewAsync(threshold, ct);
    }

    // ──────────────────────────────── Helpers ────────────────────────────────────

    private static TaskBridgeResult<T> Success<T>(T data) =>
        new(TaskBridgeOutcome.Success, data);

    private static TaskBridgeResult<T> Error<T>(TaskBridgeOutcome outcome, string error) =>
        new(outcome, Data: default, Error: error);

    private static string NormalizeSource(string? source) =>
        string.IsNullOrWhiteSpace(source) ? "iris" : source.Trim().ToLowerInvariant();

    private async Task<string?> NormalizeActorAsync(string? actorId, CancellationToken ct)
    {
        var allowedActors = AgentIdentityCatalog.BuildAllowedActorIds(await agentService.GetAllowedAgentIdsAsync(ct));
        return AgentIdentityCatalog.NormalizeActorId(actorId, allowedActors);
    }

    private static IEnumerable<DashboardTaskDto> FlattenBoard(BoardResponse board)
        => board.Offen
            .Concat(board.InProgress)
            .Concat(board.Review)
            .Concat(board.Blocked)
            .Concat(board.Done);

    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,
        ProjectId: t.ProjectId);
}
