using DeepDrftAPI.Middleware;
using DeepDrftAPI.Services;
using DeepDrftContent.Constants;
using DeepDrftContent.FileDatabase.Models;
using DeepDrftContent.Processors;
using DeepDrftData;
using DeepDrftModels.DTOs;
using DeepDrftModels.Enums;
using Microsoft.AspNetCore.Mvc;

namespace DeepDrftAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ReleaseController : ControllerBase
{
    private readonly IReleaseService _releaseService;
    private readonly UnifiedReleaseService _unifiedReleaseService;
    private readonly WaveformProfileService _waveformProfileService;
    private readonly ILogger<ReleaseController> _logger;

    public ReleaseController(
        IReleaseService releaseService,
        UnifiedReleaseService unifiedReleaseService,
        WaveformProfileService waveformProfileService,
        ILogger<ReleaseController> logger)
    {
        _releaseService = releaseService;
        _unifiedReleaseService = unifiedReleaseService;
        _waveformProfileService = waveformProfileService;
        _logger = logger;
    }

    // GET api/release?medium=session&q=text&genre=House&page=1&pageSize=20&sortColumn=Title&sortDescending=false (unauth)
    // Paged release list, optionally narrowed by medium, free-text search (q), and genre. The matching
    // medium's metadata satellite is populated; the others are null. Backs the public /archive browser.
    // Public browse data, same auth posture as GET api/track/page.
    [HttpGet]
    public async Task<ActionResult> GetReleases(
        [FromQuery] string? medium = null,
        [FromQuery] string? q = null,
        [FromQuery] string? genre = null,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? sortColumn = null,
        [FromQuery] bool sortDescending = false,
        CancellationToken ct = default)
    {
        ReleaseMedium? parsedMedium = null;
        if (!string.IsNullOrWhiteSpace(medium))
        {
            if (!Enum.TryParse<ReleaseMedium>(medium, ignoreCase: true, out var m) || !Enum.IsDefined(m))
                return BadRequest($"Unrecognised medium: {medium}");
            parsedMedium = m;
        }

        var filter = new ReleaseFilter { SearchText = q, Genre = genre };
        var result = await _releaseService.GetPagedAsync(page, pageSize, sortColumn, sortDescending, parsedMedium, filter, ct);
        if (!result.Success || result.Value is null)
        {
            var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
            _logger.LogError("GetReleases failed: {Error}", error);
            return StatusCode(500, "Failed to load releases");
        }

        return Ok(result.Value);
    }

    // GET api/release/{entryKey}/mix/waveform (unauthenticated)
    // Serves the high-res waveform datum for a Mix release as base64, reading the Mix's track datum from
    // the track-waveforms vault. 404 when the release is not a Mix, carries no waveform key, or no datum
    // is stored. Public read — addresses by the opaque EntryKey, not the int PK (§3e). The {entryKey}
    // string segment cannot collide with the ApiKey-gated POST {id:long}/mix/waveform (different verb +
    // constraint). Declared before the shorter "{entryKey}" route for clarity.
    //
    // LEGACY (phase-12 §5b): the visualizer no longer fetches through this release-addressed route — it
    // resolves the current track's datum via the track-cardinal GET api/track/{trackEntryKey}/waveform/
    // high-res. This endpoint is retained as a thin transitional delegate (it serves the identical datum,
    // since a Mix is single-track) and has no client caller today; remove it once nothing depends on the
    // release-addressed shape.
    [HttpGet("{entryKey}/mix/waveform")]
    public async Task<ActionResult> GetMixWaveform(string entryKey, CancellationToken ct = default)
    {
        var lookup = await _releaseService.GetByEntryKeyAsync(entryKey, ct);
        if (!lookup.Success)
        {
            var error = lookup.Messages.FirstOrDefault()?.Message ?? "Unknown error";
            _logger.LogError("GetMixWaveform lookup failed for {EntryKey}: {Error}", entryKey, error);
            return StatusCode(500, "Failed to load release");
        }

        var release = lookup.Value;
        var waveformEntryKey = release?.MixMetadata?.WaveformEntryKey;
        if (release is null || release.Medium != ReleaseMedium.Mix || string.IsNullOrEmpty(waveformEntryKey))
        {
            _logger.LogInformation("No mix waveform datum for release: {EntryKey}", entryKey);
            return NotFound();
        }

        var bytes = await _waveformProfileService.GetProfileAsync(waveformEntryKey, VaultConstants.TrackWaveforms);
        if (bytes is null)
        {
            _logger.LogInformation("Mix waveform key set but no datum stored for release: {EntryKey}", entryKey);
            return NotFound();
        }

        return Ok(new WaveformProfileDto
        {
            BucketCount = bytes.Length,
            Data = Convert.ToBase64String(bytes),
        });
    }

    // POST api/release/{id}/mix/waveform ([ApiKeyAuthorize], no body)
    // Server-side trigger: fetch the Mix's track audio from the vault, compute a duration-derived high-res
    // waveform via ComputeAndStoreHighResAsync, store it in the track-waveforms vault, and set
    // MixMetadata.WaveformEntryKey. 404 when the release is missing or has no stored audio; 500 on
    // compute/storage failure. Declared before "{id:long}".
    [ApiKeyAuthorize]
    [HttpPost("{id:long}/mix/waveform")]
    public async Task<ActionResult> GenerateMixWaveform(long id, CancellationToken ct = default)
    {
        var result = await _unifiedReleaseService.TriggerMixWaveformAsync(id, ct);
        if (result.Success)
            return Ok();

        var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
        if (string.Equals(error, ReleaseManager.ReleaseNotFoundMessage, StringComparison.Ordinal)
            || string.Equals(error, UnifiedReleaseService.MixTrackNoAudioMessage, StringComparison.Ordinal)
            || string.Equals(error, UnifiedReleaseService.MixHasNoTrackMessage, StringComparison.Ordinal))
        {
            return NotFound();
        }

        _logger.LogError("GenerateMixWaveform failed for {ReleaseId}: {Error}", id, error);
        return StatusCode(500, error);
    }

    // POST api/release/{id}/session/hero-image ([ApiKeyAuthorize], multipart)
    // Stores a hero image in the images vault and sets SessionMetadata.HeroImageEntryKey. The release
    // must be a Session medium (enforced in the service). Declared before "{id:long}".
    [ApiKeyAuthorize]
    [HttpPost("{id:long}/session/hero-image")]
    [RequestSizeLimit(50_000_000)]
    public async Task<ActionResult> UploadSessionHeroImage(
        long id,
        [FromForm] IFormFile? image,
        CancellationToken ct = default)
    {
        if (image is null || image.Length == 0)
            return BadRequest("Image file is required");

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

        var result = await _unifiedReleaseService.SetHeroImageAsync(id, image, ct);
        if (result.Success)
            return Ok();

        var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
        if (string.Equals(error, ReleaseManager.ReleaseNotFoundMessage, StringComparison.Ordinal))
            return NotFound();

        _logger.LogError("UploadSessionHeroImage failed for {ReleaseId}: {Error}", id, error);
        return StatusCode(500, error);
    }

    // GET api/release/{entryKey} (unauthenticated)
    // Single release with both metadata navs (nulls for non-matching media). Public read — addresses by
    // the opaque EntryKey, not the int PK (§3e). Declared after the longer "{entryKey}/mix/waveform"
    // route so the segmented route resolves first.
    [HttpGet("{entryKey}")]
    public async Task<ActionResult> GetReleaseByEntryKey(string entryKey, CancellationToken ct = default)
    {
        var result = await _releaseService.GetByEntryKeyAsync(entryKey, ct);
        if (!result.Success)
        {
            var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
            _logger.LogError("GetReleaseByEntryKey failed for {EntryKey}: {Error}", entryKey, error);
            return StatusCode(500, "Failed to load release");
        }

        if (result.Value is null)
            return NotFound();

        return Ok(result.Value);
    }
}
