using GermanApp.Application.DTOs; using GermanApp.Application.Interfaces; using GermanApp.Application.DTOs.Auth; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Net; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; namespace GermanApp.Presentation.Controllers; /// /// API controller for admin operations. /// All endpoints require Admin role. /// This is part of the Presentation layer. /// [ApiController] [Route("api/[controller]")] [Authorize(Roles = "Admin")] public class AdminController : ControllerBase { private readonly IAdminService _adminService; private readonly IUserReportService _reportService; public AdminController(IAdminService adminService, IUserReportService reportService) { _adminService = adminService; _reportService = reportService; } // ============================================ // USER MANAGEMENT ENDPOINTS // ============================================ /// /// Gets all users (Admin only). /// /// Cancellation token /// List of all users [HttpGet("users")] [ProducesResponseType(typeof(IReadOnlyList), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetAllUsersAsync(CancellationToken cancellationToken = default) { var users = await _adminService.GetAllUsersAsync(cancellationToken); return Ok(users); } /// /// Gets a specific user by ID (Admin only). /// /// The user ID /// Cancellation token /// The user DTO [HttpGet("users/{userId}")] [ProducesResponseType(typeof(AdminUserDto), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetUserByIdAsync(int userId, CancellationToken cancellationToken = default) { var user = await _adminService.GetUserByIdAsync(userId, cancellationToken); if (user == null) return NotFound(); return Ok(user); } /// /// Updates a user's role (Admin only). /// /// The user ID /// The role update data /// Cancellation token /// The updated user DTO [HttpPut("users/{userId}/role")] [ProducesResponseType(typeof(AdminUserDto), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task UpdateUserRoleAsync( int userId, [FromBody] UpdateUserRoleDto dto, CancellationToken cancellationToken = default) { try { var user = await _adminService.UpdateUserRoleAsync(userId, dto.Role, cancellationToken); if (user == null) return NotFound(); return Ok(user); } catch (ArgumentException ex) { return BadRequest(ex.Message); } } /// /// Deletes a user (Admin only, cannot delete self). /// /// The user ID to delete /// Cancellation token /// Success or error [HttpDelete("users/{userId}")] [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task DeleteUserAsync(int userId, CancellationToken cancellationToken = default) { try { // Get admin user ID from JWT claim // Note: JWT "sub" claim is mapped by ASP.NET Core JWT middleware to ClaimTypes.NameIdentifier var adminUserIdClaim = User.FindFirst(ClaimTypes.NameIdentifier) ?? User.FindFirst("sub"); if (adminUserIdClaim == null || !int.TryParse(adminUserIdClaim.Value, out var adminUserId) || adminUserId == 0) return Unauthorized(); var result = await _adminService.DeleteUserAsync(userId, adminUserId, cancellationToken); if (!result) return NotFound(); return NoContent(); } catch (InvalidOperationException ex) { return BadRequest(ex.Message); } } // ============================================ // DASHBOARD ENDPOINTS // ============================================ /// /// Gets admin dashboard statistics. /// /// Cancellation token /// Dashboard statistics [HttpGet("dashboard/stats")] [ProducesResponseType(typeof(AdminDashboardStatsDto), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetDashboardStatsAsync(CancellationToken cancellationToken = default) { var stats = await _adminService.GetDashboardStatsAsync(cancellationToken); return Ok(stats); } // ============================================ // REPORT ENDPOINTS // ============================================ /// /// Generates a progress report for a specific user. /// /// The user ID /// Cancellation token /// User progress report [HttpGet("reports/users/{userId}")] [ProducesResponseType(typeof(UserProgressReportDto), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetUserReportAsync(int userId, CancellationToken cancellationToken = default) { try { var report = await _reportService.GenerateUserReportAsync(userId, cancellationToken); return Ok(report); } catch (ArgumentException ex) { return BadRequest(ex.Message); } } /// /// Generates progress reports for all users. /// /// Cancellation token /// List of all user progress reports [HttpGet("reports/all-users")] [ProducesResponseType(typeof(IReadOnlyList), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetAllUserReportsAsync(CancellationToken cancellationToken = default) { var reports = await _reportService.GenerateAllUserReportsAsync(cancellationToken); return Ok(reports); } /// /// Gets lesson completion data for a user. /// /// The user ID /// Cancellation token /// List of lesson completion data [HttpGet("reports/users/{userId}/lessons")] [ProducesResponseType(typeof(IReadOnlyList), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetUserLessonProgressAsync(int userId, CancellationToken cancellationToken = default) { var progress = await _reportService.GetUserLessonProgressAsync(userId, cancellationToken); return Ok(progress); } /// /// Gets quiz results for a user. /// /// The user ID /// Cancellation token /// List of quiz results [HttpGet("reports/users/{userId}/quizzes")] [ProducesResponseType(typeof(IReadOnlyList), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetUserQuizResultsAsync(int userId, CancellationToken cancellationToken = default) { var results = await _reportService.GetUserQuizResultsAsync(userId, cancellationToken); return Ok(results); } /// /// Exports all user reports as CSV. /// /// Cancellation token /// CSV content [HttpGet("reports/export/csv")] [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task ExportReportsAsCsvAsync(CancellationToken cancellationToken = default) { var csv = await _reportService.ExportReportsAsCsvAsync(cancellationToken); return Ok(new { CsvContent = csv }); } /// /// Gets a specific user's full report (combined data). /// /// The user ID /// Cancellation token /// Combined user report data [HttpGet("reports/users/{userId}/full")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] public async Task GetUserFullReportAsync(int userId, CancellationToken cancellationToken = default) { try { var progressReportTask = _reportService.GenerateUserReportAsync(userId, cancellationToken); var lessonProgressTask = _reportService.GetUserLessonProgressAsync(userId, cancellationToken); var quizResultsTask = _reportService.GetUserQuizResultsAsync(userId, cancellationToken); await Task.WhenAll(progressReportTask, lessonProgressTask, quizResultsTask); return Ok(new { ProgressReport = await progressReportTask, LessonProgress = await lessonProgressTask, QuizResults = await quizResultsTask }); } catch (ArgumentException ex) { return BadRequest(ex.Message); } } }