using DeepDrftAPI.Middleware;
using DeepDrftContent.Constants;
using DeepDrftContent.FileDatabase.Models;
using DeepDrftContent.FileDatabase.Services;
using DeepDrftContent.Processors;
using Microsoft.AspNetCore.Mvc;

namespace DeepDrftAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ImageController : ControllerBase
{
    // 50 MB ceiling — cover art is small, but this is generous headroom for high-res masters.
    private const int MaxImageBytes = 50_000_000;

    // FileDatabase is injected directly because image operations are vault-only: there is no
    // SQL row for an image. The link to a track is TrackEntity.ImagePath (the entry key),
    // written separately via PUT api/track/meta/{id}.
    private readonly FileDatabase _fileDatabase;
    private readonly ImageProcessor _imageProcessor;
    private readonly ILogger<ImageController> _logger;

    public ImageController(
        FileDatabase fileDatabase,
        ImageProcessor imageProcessor,
        ILogger<ImageController> logger)
    {
        _fileDatabase = fileDatabase;
        _imageProcessor = imageProcessor;
        _logger = logger;
    }

    // POST api/image/upload ([ApiKeyAuthorize])
    // Stores a cover-art image in the images vault and returns its generated entry key. Images
    // are small enough to buffer whole in memory — no temp-file dance like the WAV upload path.
    [ApiKeyAuthorize]
    [HttpPost("upload")]
    [RequestSizeLimit(MaxImageBytes)]
    public async Task<ActionResult> UploadImage([FromForm] IFormFile? image, CancellationToken cancellationToken)
    {
        if (image is null || image.Length == 0)
        {
            return BadRequest("Image file is required");
        }

        if (image.Length > MaxImageBytes)
        {
            return BadRequest($"Image exceeds the {MaxImageBytes} byte limit");
        }

        if (MimeTypeExtensions.GetExtension(image.ContentType) == ".bin")
        {
            _logger.LogWarning("UploadImage rejected: unsupported content type '{ContentType}'", image.ContentType);
            return BadRequest($"Unsupported image content type: {image.ContentType}");
        }

        byte[] buffer;
        await using (var stream = image.OpenReadStream())
        using (var memory = new MemoryStream())
        {
            await stream.CopyToAsync(memory, cancellationToken);
            buffer = memory.ToArray();
        }

        var imageBinary = _imageProcessor.Process(buffer, image.ContentType);
        if (imageBinary is null)
        {
            // Process only returns null for an unsupported content type, already screened above —
            // belt-and-suspenders in case ImageProcessor's validation diverges later.
            _logger.LogWarning("UploadImage: ImageProcessor rejected content type '{ContentType}'", image.ContentType);
            return BadRequest($"Unsupported image content type: {image.ContentType}");
        }

        var entryKey = Guid.NewGuid().ToString("N");
        var stored = await _fileDatabase.RegisterResourceAsync(VaultConstants.Images, entryKey, imageBinary);
        if (!stored)
        {
            _logger.LogError("UploadImage: vault write failed for entryKey={EntryKey}, contentType={ContentType}, size={Size}",
                entryKey, image.ContentType, buffer.Length);
            return StatusCode(500, "Failed to store image");
        }

        _logger.LogInformation("UploadImage succeeded: entryKey={EntryKey}, contentType={ContentType}, size={Size}",
            entryKey, image.ContentType, buffer.Length);
        return Ok(new { entryKey });
    }

    // GET api/image/{entryKey} (unauthenticated)
    // Streams the image whole from disk. Same disk-streaming pattern as GET api/track/{trackId}
    // offset-0 path: File() takes ownership of the inner stream on the success path; the wrapper
    // is disposed only on the catch path.
    [HttpGet("{entryKey}")]
    public async Task<ActionResult> GetImage(string entryKey)
    {
        var vault = _fileDatabase.GetVault(VaultConstants.Images);
        if (vault is null)
        {
            _logger.LogWarning("Images vault not found");
            return NotFound();
        }

        var mediaStream = await vault.GetEntryStreamAsync(entryKey);
        if (mediaStream is null)
        {
            _logger.LogWarning("Image not found: {EntryKey}", entryKey);
            return NotFound();
        }

        string mimeType;
        Stream innerStream;
        try
        {
            mimeType = MimeTypeExtensions.GetMimeType(mediaStream.Extension);
            innerStream = mediaStream.Stream;
        }
        catch
        {
            await mediaStream.DisposeAsync();
            throw;
        }

        return File(innerStream, mimeType, enableRangeProcessing: false);
    }
}
