using Microsoft.AspNetCore.Mvc; using TuneRipper.Shared.Models; using TuneRipper.Api.Services; using System.Collections.Concurrent; namespace TuneRipper.Api.Controllers; [ApiController] [Route("api/[controller]")] public class VideoController : ControllerBase { private readonly IVideoService _videoService; private readonly ILogger _logger; // In-memory storage for processing status (in production, use database or Redis) private static readonly ConcurrentDictionary _processingJobs = new(); private class ProcessingJob { public string Id { get; set; } = string.Empty; public ProcessVideoRequest Request { get; set; } = new(); public DownloadProgress Progress { get; set; } = new(); public ProcessVideoResponse? Result { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } public VideoController(IVideoService videoService, ILogger logger) { _videoService = videoService; _logger = logger; } [HttpGet("details")] public async Task> GetVideoDetails([FromQuery] string url) { if (string.IsNullOrWhiteSpace(url)) { return BadRequest("URL is required"); } if (!_videoService.IsValidYouTubeUrl(url)) { return BadRequest("Invalid YouTube URL"); } try { _logger.LogInformation("Fetching video details for URL: {Url}", url); var videoDetails = await _videoService.GetVideoDetailsAsync(url); if (videoDetails == null) { return NotFound("Video not found or unavailable"); } return Ok(videoDetails); } catch (Exception ex) { _logger.LogError(ex, "Error fetching video details for URL: {Url}", url); return StatusCode(500, $"Error fetching video details: {ex.Message}"); } } [HttpPost("process")] public async Task> StartProcessing([FromBody] ProcessVideoRequest request) { if (request == null || string.IsNullOrWhiteSpace(request.Url)) { return BadRequest("Invalid request"); } if (!_videoService.IsValidYouTubeUrl(request.Url)) { return BadRequest("Invalid YouTube URL"); } try { _logger.LogInformation("Starting video processing for URL: {Url}", request.Url); // Generate a job ID var jobId = Guid.NewGuid().ToString(); // Create processing job var job = new ProcessingJob { Id = jobId, Request = request, Progress = new DownloadProgress { PercentageComplete = 0, Status = "Queued", Message = "Processing request queued" } }; _processingJobs[jobId] = job; // Start processing in background (fire and forget) _ = Task.Run(() => ProcessVideoAsync(jobId)); var response = new ProcessVideoResponse { Success = true, Message = "Processing started", FileName = jobId, // Use job ID as temporary filename DownloadUrl = null, // Will be available when processing completes VideoDetails = null // Will be populated during processing }; return Accepted(response); } catch (Exception ex) { _logger.LogError(ex, "Error starting video processing for URL: {Url}", request.Url); return StatusCode(500, $"Error starting video processing: {ex.Message}"); } } [HttpGet("status/{jobId}")] public ActionResult GetProcessingStatus(string jobId) { if (string.IsNullOrWhiteSpace(jobId)) { return BadRequest("Job ID is required"); } if (!_processingJobs.TryGetValue(jobId, out var job)) { return NotFound("Job not found"); } return Ok(job.Progress); } [HttpGet("result/{jobId}")] public ActionResult GetProcessingResult(string jobId) { if (string.IsNullOrWhiteSpace(jobId)) { return BadRequest("Job ID is required"); } if (!_processingJobs.TryGetValue(jobId, out var job)) { return NotFound("Job not found"); } if (job.Result == null) { return Accepted("Processing not completed yet"); } return Ok(job.Result); } [HttpGet("jobs")] public ActionResult> GetAllJobs() { var jobs = _processingJobs.Values.Select(job => new JobStatusResponse { JobId = job.Id, Status = job.Progress.Status, PercentageComplete = job.Progress.PercentageComplete, Message = job.Progress.Message, CreatedAt = job.CreatedAt, CompletedAt = job.Result != null ? job.CreatedAt.AddMinutes(5) : null, // Simulated completion time Result = job.Result }).OrderByDescending(j => j.CreatedAt); return Ok(jobs); } private async Task ProcessVideoAsync(string jobId) { if (!_processingJobs.TryGetValue(jobId, out var job)) return; try { _logger.LogInformation("Processing video for job {JobId}", jobId); // Simulate video processing steps var steps = new[] { ("Fetching video information", 10), ("Analyzing audio stream", 25), ("Downloading audio data", 60), ("Converting to MP3", 85), ("Finalizing", 100) }; foreach (var (message, percentage) in steps) { job.Progress.Status = "Processing"; job.Progress.Message = message; job.Progress.PercentageComplete = percentage; _logger.LogInformation("Job {JobId}: {Message} ({Percentage}%)", jobId, message, percentage); // Simulate processing time await Task.Delay(Random.Shared.Next(1000, 3000)); } // Get video details using the service var videoDetails = await _videoService.GetVideoDetailsAsync(job.Request.Url!); // Complete processing job.Progress.Status = "Completed"; job.Progress.Message = "Processing completed successfully"; job.Progress.PercentageComplete = 100; job.Result = new ProcessVideoResponse { Success = true, Message = "Video processed successfully", FileName = $"{videoDetails?.Title?.Replace(" ", "_") ?? "video"}.{job.Request.Format ?? "mp3"}", DownloadUrl = $"/api/download/{jobId}", VideoDetails = videoDetails }; _logger.LogInformation("Successfully completed processing for job {JobId}", jobId); } catch (Exception ex) { _logger.LogError(ex, "Error processing video for job {JobId}", jobId); job.Progress.Status = "Failed"; job.Progress.Message = $"Processing failed: {ex.Message}"; job.Progress.PercentageComplete = 0; job.Result = new ProcessVideoResponse { Success = false, Message = ex.Message, FileName = null, DownloadUrl = null, VideoDetails = null }; } } }