using Microsoft.AspNetCore.Mvc; using TaskTracker.Core.DTOs; using TaskTracker.Core.Entities; using TaskTracker.Core.Enums; using TaskTracker.Core.Interfaces; namespace TaskTracker.Api.Controllers; [ApiController] [Route("api/[controller]")] public class TasksController(ITaskRepository taskRepo, ILogger logger) : ControllerBase { [HttpGet] public async Task GetAll([FromQuery] WorkTaskStatus? status, [FromQuery] int? parentId, [FromQuery] bool includeSubTasks = false) { var tasks = await taskRepo.GetAllAsync(status, parentId, includeSubTasks); return Ok(ApiResponse>.Ok(tasks)); } [HttpGet("active")] public async Task GetActive() { var task = await taskRepo.GetActiveTaskAsync(); if (task is null) return Ok(ApiResponse.Ok(null)); return Ok(ApiResponse.Ok(task)); } [HttpGet("{id:int}")] public async Task GetById(int id) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); return Ok(ApiResponse.Ok(task)); } [HttpPost] public async Task Create([FromBody] CreateTaskRequest request) { if (request.ParentTaskId.HasValue) { var parent = await taskRepo.GetByIdAsync(request.ParentTaskId.Value); if (parent is null) return BadRequest(ApiResponse.Fail("Parent task not found")); } var task = new WorkTask { Title = request.Title, Description = request.Description, Category = request.Category, ParentTaskId = request.ParentTaskId, EstimatedMinutes = request.EstimatedMinutes, Status = WorkTaskStatus.Pending }; var created = await taskRepo.CreateAsync(task); logger.LogInformation("Created task {TaskId}: {Title}", created.Id, created.Title); return CreatedAtAction(nameof(GetById), new { id = created.Id }, ApiResponse.Ok(created)); } [HttpPut("{id:int}/start")] public async Task Start(int id) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); // Pause any currently active task var active = await taskRepo.GetActiveTaskAsync(); if (active is not null && active.Id != id) { active.Status = WorkTaskStatus.Paused; await taskRepo.UpdateAsync(active); } task.Status = WorkTaskStatus.Active; task.StartedAt ??= DateTime.UtcNow; await taskRepo.UpdateAsync(task); logger.LogInformation("Started task {TaskId}", id); return Ok(ApiResponse.Ok(task)); } [HttpPut("{id:int}/pause")] public async Task Pause(int id, [FromBody] TaskActionRequest? request) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); task.Status = WorkTaskStatus.Paused; if (!string.IsNullOrWhiteSpace(request?.Note)) { task.Notes.Add(new TaskNote { Content = request.Note, Type = NoteType.PauseNote, CreatedAt = DateTime.UtcNow }); } await taskRepo.UpdateAsync(task); logger.LogInformation("Paused task {TaskId}", id); return Ok(ApiResponse.Ok(task)); } [HttpPut("{id:int}/resume")] public async Task Resume(int id, [FromBody] TaskActionRequest? request) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); // Pause any currently active task var active = await taskRepo.GetActiveTaskAsync(); if (active is not null && active.Id != id) { active.Status = WorkTaskStatus.Paused; await taskRepo.UpdateAsync(active); } task.Status = WorkTaskStatus.Active; if (!string.IsNullOrWhiteSpace(request?.Note)) { task.Notes.Add(new TaskNote { Content = request.Note, Type = NoteType.ResumeNote, CreatedAt = DateTime.UtcNow }); } await taskRepo.UpdateAsync(task); logger.LogInformation("Resumed task {TaskId}", id); return Ok(ApiResponse.Ok(task)); } [HttpPut("{id:int}/complete")] public async Task Complete(int id) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); // Block completion if any subtasks are not completed/abandoned var incompleteSubTasks = task.SubTasks .Where(st => st.Status != WorkTaskStatus.Completed && st.Status != WorkTaskStatus.Abandoned) .ToList(); if (incompleteSubTasks.Count > 0) return BadRequest(ApiResponse.Fail($"Cannot complete task: {incompleteSubTasks.Count} subtask(s) are still incomplete")); task.Status = WorkTaskStatus.Completed; task.CompletedAt = DateTime.UtcNow; await taskRepo.UpdateAsync(task); logger.LogInformation("Completed task {TaskId}", id); return Ok(ApiResponse.Ok(task)); } [HttpPut("{id:int}")] public async Task Update(int id, [FromBody] UpdateTaskRequest request) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); if (request.Title is not null) task.Title = request.Title; if (request.Description is not null) task.Description = request.Description; if (request.Category is not null) task.Category = request.Category; if (request.EstimatedMinutes.HasValue) task.EstimatedMinutes = request.EstimatedMinutes; await taskRepo.UpdateAsync(task); return Ok(ApiResponse.Ok(task)); } [HttpDelete("{id:int}")] public async Task Delete(int id) { await taskRepo.DeleteAsync(id); logger.LogInformation("Abandoned task {TaskId}", id); return Ok(ApiResponse.Ok()); } }