using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Nexus.Api.Controllers;
using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Repositories;
using Nexus.Api.Services;
using Xunit;

namespace Nexus.Api.Tests;

public sealed class TaskWorkflowTests
{
    [Fact]
    public async Task TaskMutation_PublishesContentMinimizedLegacyInvalidation()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        using var subscriptionLifetime = new CancellationTokenSource();
        var subscription = await fixture.LiveUpdateService.SubscribeAsync(
            ct: subscriptionLifetime.Token);

        var task = await fixture.TaskService.CreateAsync(
            new Nexus.Api.DTOs.CreateTaskRequest("Mutation delta", "Normal", null),
            CancellationToken.None);

        var update = await subscription.Reader.ReadAsync(CancellationToken.None);
        if (update.Type != "tasks.board.snapshot")
            update = await subscription.Reader.ReadAsync(CancellationToken.None);
        subscriptionLifetime.Cancel();

        Assert.Equal("tasks.board.snapshot", update.Type);
        Assert.Equal("board", update.Channel);
        Assert.IsNotType<BoardResponse>(update.Payload);
        var payload = JsonSerializer.Serialize(update.Payload);
        Assert.Contains(task.Id.ToString(), payload, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("Mutation delta", payload, StringComparison.Ordinal);
    }

    [Fact]
    public async Task CreateAgentTaskAsync_PreservesConfiguredAssigneeAndBacklogState_WhenPlannedChildTask()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var parent = await fixture.TaskService.CreateDashboardTaskAsync(
            "Parent", "Coordination", "iris", "High", "iris", null, CancellationToken.None);

        var child = await fixture.TaskService.CreateAgentTaskAsync(
            "PO spec",
            "Prepare specification",
            "iris",
            "Medium",
            "product-owner",
            "programmer-fast",
            parent.Id,
            startsInProgress: false,
            initialState: null,
            ct: CancellationToken.None);

        Assert.Equal("Backlog", child.State);
        Assert.Equal("product-owner", child.AssignedTo);
        Assert.Equal("programmer-fast", child.ExpectedFrom);
        Assert.True(child.IsAgentTask);
    }

    [Fact]
    public async Task GetDashboardTaskByIdAsync_MapsChildDelegationAndActivity()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var parent = await fixture.TaskService.CreateDashboardTaskAsync(
            "Parent", null, "iris", "High", "iris", null, CancellationToken.None);

        var child = await fixture.TaskService.CreateAgentTaskAsync(
            "Implement",
            "Code changes",
            "iris",
            "High",
            "programmer-fast",
            "programmer-fast",
            parent.Id,
            startsInProgress: false,
            initialState: null,
            ct: CancellationToken.None);

        var dto = await fixture.TaskService.GetDashboardTaskByIdAsync(child.Id, CancellationToken.None);

        Assert.NotNull(dto);
        Assert.True(dto!.HasVisibleDelegation);
        Assert.NotNull(dto.LastActivityMessage);
        Assert.Equal("programmer-fast", dto.AssignedTo);
        Assert.Equal("programmer-fast", dto.ExpectedFrom);
    }

    [Fact]
    public async Task BridgeGetChildTasksAsync_ReturnsMappedActivityAndVisibleDelegation()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var parent = await fixture.TaskService.CreateDashboardTaskAsync(
            "Parent", null, "iris", "High", "iris", null, CancellationToken.None);

        await fixture.TaskBridgeService.CreateChildTaskAsync(
            parent.Id,
            "Review",
            "Review implementation",
            "iris",
            "Medium",
            "reviewer",
            "reviewer",
            startsInProgress: false,
            ct: CancellationToken.None);

        var children = await fixture.TaskBridgeService.GetChildTasksAsync(parent.Id, CancellationToken.None);
        var child = Assert.Single(children);

        Assert.True(child.HasVisibleDelegation);
        Assert.NotNull(child.LastActivityMessage);
        Assert.Equal("reviewer", child.AssignedTo);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_RejectsAgentHeaderWithoutAuthentication()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Agent-Id"] = "programmer-fast"
                })
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        Assert.IsType<UnauthorizedObjectResult>(result.Result);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_AcceptsAgentHintAfterServiceAuthentication()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Agent-Id"] = "programmer-fast",
                    ["X-Nexus-Api-Key"] = "test-service-key"
                })
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        Assert.IsType<OkObjectResult>(result.Result);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_AcceptsServiceKeyWithoutConfiguredNexusSystemAgent()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Nexus-Api-Key"] = "test-service-key"
                })
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        Assert.IsType<OkObjectResult>(result.Result);
    }

    [Fact]
    public async Task DashboardController_GetBoard_AcceptsServiceKeyWithoutJwt()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new DashboardController(
            new FakeDashboardService(),
            fixture.TaskService,
            fixture.ActivityRepository,
            new HttpContextAccessor(),
            fixture.AgentService,
            fixture.Configuration,
            fixture.NotificationService,
            fixture.LiveUpdateService)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Nexus-Api-Key"] = "test-service-key"
                })
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        Assert.IsType<OkObjectResult>(result.Result);
    }

    [Fact]
    public async Task TasksController_GetBoard_RejectsAgentHeaderWithoutAuthentication()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Agent-Id"] = "programmer-fast"
                })
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status401Unauthorized);
    }

    [Fact]
    public async Task TasksController_ResetStale_Anonymous_IsUnauthorized()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext()
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status401Unauthorized);
    }

    [Fact]
    public async Task TasksController_ResetStale_UnknownAgentHeaderWithoutAuthentication_IsUnauthorized()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Agent-Id"] = "unknown-agent"
                })
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status401Unauthorized);
    }

    [Fact]
    public async Task TasksController_ResetStale_OrdinaryJwtUser_IsForbidden()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(user: TaskWorkflowFixture.CreateUser("user-1", "user"))
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status403Forbidden);
    }

    [Fact]
    public async Task TasksController_ResetStale_OrdinaryJwtCannotEscalateWithIrisHeader()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(
                    agentId: "iris",
                    user: TaskWorkflowFixture.CreateUser("user-1", "user"))
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status403Forbidden);
    }

    [Fact]
    public async Task TasksController_ResetStale_ServiceKey_IsAllowed()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Nexus-Api-Key"] = "test-service-key"
                })
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status200OK);
    }

    [Fact]
    public async Task TasksController_ResetStale_IrisHeaderWithoutAuthentication_IsUnauthorized()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(agentId: "iris")
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status401Unauthorized);
    }

    [Fact]
    public async Task TasksController_ResetStale_IrisHintWithServiceKey_IsAllowed()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new TasksController(fixture.TaskService, fixture.AgentService, fixture.Configuration, fixture.ActivityRepository)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(headers: new Dictionary<string, string>
                {
                    ["X-Agent-Id"] = "iris",
                    ["X-Nexus-Api-Key"] = "test-service-key"
                })
            }
        };

        var result = await controller.ResetStale(new ResetStaleRequest(2), CancellationToken.None);

        AssertStatusCode(result, StatusCodes.Status200OK);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_OrdinaryJwtUser_IsForbidden()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(user: TaskWorkflowFixture.CreateUser("user-1", "user"))
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        var forbidden = Assert.IsType<ObjectResult>(result.Result);
        Assert.Equal(StatusCodes.Status403Forbidden, forbidden.StatusCode);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_OrdinaryJwtCannotEscalateWithAgentHeader()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(
                    agentId: "iris",
                    user: TaskWorkflowFixture.CreateUser("user-1", "user"))
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        var forbidden = Assert.IsType<ObjectResult>(result.Result);
        Assert.Equal(StatusCodes.Status403Forbidden, forbidden.StatusCode);
    }

    [Fact]
    public async Task GatewayBridgeController_GetBoard_AdminJwt_IsAllowed()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();

        var controller = new GatewayBridgeController(
            fixture.TaskBridgeService,
            fixture.AgentService,
            fixture.Configuration,
            NullLogger<GatewayBridgeController>.Instance)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = TaskWorkflowFixture.CreateHttpContext(user: TaskWorkflowFixture.CreateUser("bao", "admin"))
            }
        };

        var result = await controller.GetBoard(CancellationToken.None);
        Assert.IsType<OkObjectResult>(result.Result);
    }

    [Fact]
    public async Task CreateChildTaskAsync_TransitionsBacklogParent_WhenCallerIsProgrammerFast()
    {
        await using var fixture = await TaskWorkflowFixture.CreateAsync();
        fixture.SetCallerAgent("programmer-fast");

        var parent = await fixture.TaskService.CreateDashboardTaskAsync(
            "Parent", "Coordination", "iris", "High", "iris", null, CancellationToken.None);

        var result = await fixture.TaskBridgeService.CreateChildTaskAsync(
            parent.Id,
            "Implement",
            "Ship the change",
            "programmer-fast",
            "Medium",
            "programmer-fast",
            "programmer-fast",
            startsInProgress: false,
            ct: CancellationToken.None);

        var updatedParent = await fixture.TaskService.GetByIdAsync(parent.Id, CancellationToken.None);

        Assert.Equal(TaskBridgeOutcome.Success, result.Outcome);
        Assert.NotNull(updatedParent);
        Assert.Equal("In progress", updatedParent!.State);
    }

    private static void AssertStatusCode(IResult result, int expectedStatusCode)
    {
        if (expectedStatusCode == StatusCodes.Status403Forbidden)
        {
            Assert.Equal("Microsoft.AspNetCore.Http.HttpResults.ForbidHttpResult", result.GetType().FullName);
            return;
        }

        var statusResult = Assert.IsAssignableFrom<IStatusCodeHttpResult>(result);
        Assert.Equal(expectedStatusCode, statusResult.StatusCode);
    }
}

internal sealed class TaskWorkflowFixture : IAsyncDisposable
{
    private readonly NexusDbContext _db;

    private TaskWorkflowFixture(
        NexusDbContext db,
        IConfiguration configuration,
        ITaskRepository taskRepository,
        IActivityRepository activityRepository,
        INotificationService notificationService,
        ILiveUpdateService liveUpdateService,
        IStaleTaskRecoveryService staleTaskRecoveryService,
        ITaskService taskService,
        ITaskBridgeService taskBridgeService,
        IAgentService agentService,
        HttpContextAccessor httpContextAccessor)
    {
        _db = db;
        Configuration = configuration;
        TaskRepository = taskRepository;
        ActivityRepository = activityRepository;
        NotificationService = notificationService;
        LiveUpdateService = liveUpdateService;
        StaleTaskRecoveryService = staleTaskRecoveryService;
        TaskService = taskService;
        TaskBridgeService = taskBridgeService;
        AgentService = agentService;
        HttpContextAccessor = httpContextAccessor;
    }

    public IConfiguration Configuration { get; }
    public ITaskRepository TaskRepository { get; }
    public IActivityRepository ActivityRepository { get; }
    public INotificationService NotificationService { get; }
    public ILiveUpdateService LiveUpdateService { get; }
    public IStaleTaskRecoveryService StaleTaskRecoveryService { get; }
    public ITaskService TaskService { get; }
    public ITaskBridgeService TaskBridgeService { get; }
    public IAgentService AgentService { get; }
    public HttpContextAccessor HttpContextAccessor { get; }

    public static async Task<TaskWorkflowFixture> CreateAsync()
    {
        var options = new DbContextOptionsBuilder<NexusDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        var db = new NexusDbContext(options);
        await db.Database.EnsureCreatedAsync();

        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["NexusApiKey"] = "test-service-key"
            })
            .Build();

        var agentService = new AgentService(new StubOpenClawControlService());
        var liveUpdateService = new LiveUpdateService();
        var activityRepository = new ActivityRepository(db, liveUpdateService);
        var taskRepository = new TaskRepository(db);
        var notificationService = new NotificationService(db, liveUpdateService);
        var httpContextAccessor = new HttpContextAccessor
        {
            HttpContext = CreateHttpContext(
                agentId: "iris",
                user: CreateUser("service", "Service"))
        };
        var staleTaskRecoveryService = new StaleTaskRecoveryService(
            taskRepository,
            activityRepository,
            liveUpdateService);

        var taskService = new TaskService(
            taskRepository,
            activityRepository,
            notificationService,
            agentService,
            httpContextAccessor,
            liveUpdateService,
            staleTaskRecoveryService);

        var taskBridgeService = new TaskBridgeService(
            taskService,
            agentService,
            activityRepository,
            notificationService,
            liveUpdateService);

        return new TaskWorkflowFixture(
            db,
            configuration,
            taskRepository,
            activityRepository,
            notificationService,
            liveUpdateService,
            staleTaskRecoveryService,
            taskService,
            taskBridgeService,
            agentService,
            httpContextAccessor);
    }

    public static DefaultHttpContext CreateHttpContext(
        string? agentId = null,
        Dictionary<string, string>? headers = null,
        ClaimsPrincipal? user = null)
    {
        var httpContext = new DefaultHttpContext();
        if (!string.IsNullOrWhiteSpace(agentId))
            httpContext.Request.Headers["X-Agent-Id"] = agentId;

        if (headers is not null)
        {
            foreach (var (key, value) in headers)
                httpContext.Request.Headers[key] = value;
        }

        httpContext.User = user ?? new ClaimsPrincipal(new ClaimsIdentity());
        return httpContext;
    }

    public static ClaimsPrincipal CreateUser(string userId, string role)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, userId),
            new Claim(ClaimTypes.Role, role)
        };

        return new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
    }

    public void SetCallerAgent(string agentId)
    {
        HttpContextAccessor.HttpContext = CreateHttpContext(
            agentId: agentId,
            user: CreateUser("service", "Service"));
    }

    public async ValueTask DisposeAsync()
    {
        await _db.DisposeAsync();
    }

}

file sealed class FakeDashboardService : IDashboardService
{
    public Task<DashboardStatus> GetStatusAsync() => Task.FromResult(new DashboardStatus(true, "online", 1, 0));
    public Task<List<DashboardAgentInfo>> GetAgentsAsync() => Task.FromResult(new List<DashboardAgentInfo>());
    public Task<List<FeedEntry>> GetOperationsAsync(int limit, string? agentFilter) => Task.FromResult(new List<FeedEntry>());
    public Task<ChatResponse> SendChatAsync(string agentId, string message) => Task.FromResult(new ChatResponse(true, "", null));
    public Task<List<MessageEntry>> GetMessagesAsync(string? sessionKey, int limit, int offset) => Task.FromResult(new List<MessageEntry>());
    public Task<List<QueueItem>> GetQueueAsync(CancellationToken ct) => Task.FromResult(new List<QueueItem>());
    public Task<GatewayRuntimeInfo> GetGatewayInfoAsync(CancellationToken ct) => Task.FromResult(new GatewayRuntimeInfo(true, "http://gateway", "test", "test", true, true, "matched", DateTimeOffset.UtcNow, "ok"));
    public Task<QueueDeleteResult> DeleteQueueItemAsync(string id, string? source, CancellationToken ct) => Task.FromResult(new QueueDeleteResult(QueueDeleteOutcome.Ignored));
    public Task<QueuePriorityResult> CycleQueuePriorityAsync(string id, CancellationToken ct) => Task.FromResult(new QueuePriorityResult(QueuePriorityOutcome.Ignored));
    public Task<AgentModelInfo?> GetAgentModelAsync(string agentId) => Task.FromResult<AgentModelInfo?>(null);
    public Task<bool> SetAgentModelAsync(string agentId, string model) => Task.FromResult(false);
    public Task<List<AgentActivityEntry>> GetAgentActivityAsync(string agentId, int limit) => Task.FromResult(new List<AgentActivityEntry>());
    public Task<List<ModelOption>> GetAvailableModelsAsync(CancellationToken ct)
        => Task.FromResult(new List<ModelOption>());
}
