using AVSCartonShipmentVerifier.DTOs; using AVSCartonShipmentVerifier.Repositories; using AVSCartonShipmentVerifier.Services; using Microsoft.AspNetCore.Mvc; namespace AVSCartonShipmentVerifier.Controllers; [Route("api/verification")] public sealed class VerificationController( IShipmentVerificationService shipmentVerificationService, IScanHistoryStore scanHistoryStore, ICartonVerificationRepository cartonVerificationRepository, IMovementAnalyticsService movementAnalyticsService, ILogger logger) : Controller { private readonly IShipmentVerificationService _shipmentVerificationService = shipmentVerificationService; private readonly IScanHistoryStore _scanHistoryStore = scanHistoryStore; private readonly ICartonVerificationRepository _cartonVerificationRepository = cartonVerificationRepository; private readonly IMovementAnalyticsService _movementAnalyticsService = movementAnalyticsService; private readonly ILogger _logger = logger; [HttpPost("scan")] [ValidateAntiForgeryToken] public async Task Scan([FromBody] QrScanRequestDto request, CancellationToken cancellationToken) { if (request is null) { return BadRequest(new { message = "Request payload is required." }); } try { var verificationResult = await _shipmentVerificationService.VerifyQrAsync( request.QrValue, request.EntryMethod ?? "Scanner", cancellationToken); var availableCartons = await TryGetAvailableCartonCountAsync(cancellationToken); return Ok(new { result = verificationResult, recentSuccessfulScans = await _scanHistoryStore.GetLastFiveSuccessfulScansAsync(cancellationToken), recentRejectedScans = await _scanHistoryStore.GetLastFiveRejectedScansAsync(cancellationToken), availableCartons }); } catch (OperationCanceledException) { return StatusCode(StatusCodes.Status499ClientClosedRequest); } catch (Exception exception) { _logger.LogError(exception, "Unexpected error while verifying QR scan."); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An unexpected error occurred during verification." }); } } [HttpGet("dashboard-summary")] public async Task GetDashboardSummary(CancellationToken cancellationToken) { var count = await TryGetAvailableCartonCountAsync(cancellationToken); if (count is null) { return StatusCode(StatusCodes.Status503ServiceUnavailable, new { message = "Unable to load available cartons count." }); } return Ok(new { availableCartons = count.Value }); } [HttpGet("available-cartons")] public async Task GetAvailableCartons(CancellationToken cancellationToken) { var count = await TryGetAvailableCartonCountAsync(cancellationToken); if (count is null) { return StatusCode(StatusCodes.Status503ServiceUnavailable, new { message = "Unable to load available cartons count." }); } return Ok(new { availableCartons = count.Value }); } [HttpGet("movement-analytics")] public async Task GetMovementAnalytics( [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, CancellationToken cancellationToken) { var today = DateOnly.FromDateTime(DateTime.Now); var toDate = to ?? today; var fromDate = from ?? toDate.AddDays(-6); try { var analytics = await _movementAnalyticsService.GetAnalyticsAsync(fromDate, toDate, cancellationToken); return Ok(analytics); } catch (Exception exception) { _logger.LogError(exception, "Unexpected error while loading movement analytics."); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Unable to load movement analytics." }); } } /// /// Returns all successful or all rejected scan records from SQLite (not limited to five). /// [HttpGet("history")] public async Task GetFullHistory([FromQuery] string category = "successful", CancellationToken cancellationToken = default) { var normalized = category.Trim().ToLowerInvariant(); try { if (normalized == "successful") { var records = await _scanHistoryStore.GetAllSuccessfulScansAsync(cancellationToken); return Ok(new { category = "successful", records }); } if (normalized == "rejected") { var records = await _scanHistoryStore.GetAllRejectedScansAsync(cancellationToken); return Ok(new { category = "rejected", records }); } return BadRequest(new { message = "Invalid category. Use 'successful' or 'rejected'." }); } catch (Exception exception) { _logger.LogError(exception, "Unexpected error while loading scan history."); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Unable to load scan history." }); } } private async Task TryGetAvailableCartonCountAsync(CancellationToken cancellationToken) { try { return await _cartonVerificationRepository.GetAvailableCartonCountAsync(cancellationToken); } catch (Exception exception) { _logger.LogWarning(exception, "Failed to load available cartons count from verification database."); return null; } } }