using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Nexus.Api.DTOs;
using Nexus.Api.Models;
using Nexus.Api.Services;

namespace Nexus.Api.Controllers;

[Authorize]
[ApiController]
[Route("api/v1/projects")]
public class ProjectsController(IProjectService projectService) : ControllerBase
{
    [HttpGet]
    [ProducesResponseType(typeof(IReadOnlyList<ProjectDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<IReadOnlyList<ProjectDto>>> GetAll(CancellationToken ct)
        => Ok((await projectService.GetAllAsync(ct)).Select(project => Map(project)).ToArray());

    [HttpGet("{id:guid}")]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<ActionResult<ProjectDto>> GetById(Guid id, CancellationToken ct)
    {
        var project = await projectService.GetByIdAsync(id, ct);
        return project is null ? NotFound() : Ok(Map(project));
    }

    [HttpGet("{id:guid}/tasks")]
    [ProducesResponseType(typeof(IReadOnlyList<ProjectTaskDto>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<ActionResult<IReadOnlyList<ProjectTaskDto>>> GetTasks(
        Guid id,
        CancellationToken ct)
    {
        if (await projectService.GetByIdAsync(id, ct) is null)
            return NotFound();

        var tasks = await projectService.GetTasksAsync(id, ct);
        return Ok(tasks.Select(task => new ProjectTaskDto(
            task.Id,
            task.Title,
            task.State,
            task.Priority,
            id,
            task.AssignedTo,
            task.ExpectedFrom,
            task.IsAgentTask,
            task.UpdatedAt)).ToArray());
    }

    [HttpPost]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<ProjectDto>> Create(
        [FromBody] CreateProjectRequest request,
        CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.Name))
        {
            ModelState.AddModelError("name", "Name is required.");
            return ValidationProblem(ModelState);
        }

        var project = await projectService.CreateAsync(request, ct);
        return Created(
            $"/api/v1/projects/{project.Id}",
            Map(project, ProjectOperation(project, "created")));
    }

    [HttpPatch("{id:guid}")]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<ActionResult<ProjectDto>> Update(
        Guid id,
        [FromBody] UpdateProjectRequest request,
        CancellationToken ct)
    {
        var project = await projectService.UpdateAsync(id, request, ct);
        return project is null
            ? NotFound()
            : Ok(Map(project, ProjectOperation(project, "updated")));
    }

    [HttpDelete("{id:guid}")]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
    {
        var result = await projectService.DeleteAsync(id, ct);
        return result.Outcome switch
        {
            ProjectDeleteOutcome.NotFound => NotFound(),
            ProjectDeleteOutcome.Archived => Ok(Map(
                result.Project!,
                ProjectOperation(result.Project!, "archived"))),
            _ => Ok(Map(
                result.Project!,
                ProjectOperation(result.Project!, "deleted")))
        };
    }

    private OperationResultDto ProjectOperation(
        Nexus.Api.Data.Project project,
        string status)
        => OperationResultFactory.FromHttpContext(
            HttpContext,
            status,
            new EntityRefDto("project", project.Id.ToString(), project.Name));

    private static ProjectDto Map(
        Nexus.Api.Data.Project project,
        OperationResultDto? operation = null)
        => new(
            project.Id,
            project.Name,
            project.Description,
            project.Status.ToString(),
            project.Progress,
            project.UpdatedAt,
            operation);
}
