@page "/tracks/upload"
@using System.Security.Claims
@using DeepDrftManager.Services
@using DeepDrftModels.Enums
@using Microsoft.AspNetCore.Components.Forms
@attribute [Authorize]

@inject ICmsTrackService CmsTrackService
@inject ICmsReleaseService CmsReleaseService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@inject ILogger<BatchUpload> Logger
@inject CmsTrackBrowserViewModel VM

<PageTitle>Upload Release — DeepDrft CMS</PageTitle>

<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
    <MudText Typo="Typo.h4" GutterBottom="true">Upload Release</MudText>

    <AlbumHeaderFields @bind-AlbumName="_albumName"
                       @bind-Artist="_artist"
                       @bind-Genre="_genre"
                       @bind-ReleaseDate="_releaseDate"
                       @bind-ReleaseType="_releaseType"
                       Medium="_medium"
                       MediumChanged="OnMediumChanged"
                       @bind-SelectedImageFile="_selectedImageFile"
                       Disabled="_uploading" />

    @if (_medium == ReleaseMedium.Cut)
    {
        <MudGrid>
            <MudItem xs="12" md="5">
                <BatchTrackList Tracks="_tracks"
                                @bind-SelectedIndex="_selectedIndex"
                                Disabled="_uploading"
                                OnWavFilesSelected="HandleWavFilesSelected"
                                OnMoveUp="MoveUp"
                                OnMoveDown="MoveDown"
                                OnRemove="RemoveRow" />
            </MudItem>

            <MudItem xs="12" md="7">
                <MudPaper Class="pa-4" Elevation="2">
                    <BatchTrackDetail SelectedTrack="@(_selectedIndex >= 0 && _tracks.Count > 0 ? _tracks[_selectedIndex] : null)"
                                      Disabled="_uploading"
                                      TrackNameChanged="@(name => { if (_selectedIndex >= 0) { _tracks[_selectedIndex].TrackName = name; } })" />
                </MudPaper>
            </MudItem>
        </MudGrid>
    }
    else
    {
        @* Session/Mix are single-track releases — no multi-track master list. A single WAV slot. *@
        <MudPaper Class="pa-4" Elevation="2">
            <MudStack Spacing="3">
                <MudText Typo="Typo.subtitle1">Track</MudText>
                <InputFile OnChange="HandleSingleWavSelected" accept=".wav,audio/wav,audio/x-wav" disabled="@_uploading" />
                @if (_tracks.Count > 0)
                {
                    @* Track name is derived from the Release Name for Session/Mix — no separate input. *@
                    <MudText Typo="Typo.caption">Selected: @(_tracks[0].WavFile?.Name ?? "—")</MudText>
                }
            </MudStack>
        </MudPaper>
    }

    @if (!string.IsNullOrEmpty(_errorMessage))
    {
        <MudAlert Severity="Severity.Error" Class="mt-4">@_errorMessage</MudAlert>
    }

    <MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2" Class="mt-4">
        <MudButton Variant="Variant.Text"
                   OnClick="@(() => Navigation.NavigateTo("/tracks"))"
                   Disabled="_uploading">
            Cancel
        </MudButton>
        <MudButton Variant="Variant.Filled"
                   Color="Color.Primary"
                   OnClick="SubmitAsync"
                   Disabled="@(_uploading || _tracks.Count == 0)">
            @if (_uploading)
            {
                <MudProgressCircular Indeterminate="true" Size="Size.Small" Class="mr-2" />
                <text>Uploading @_uploadedCount / @_tracks.Count…</text>
            }
            else
            {
                <text>Upload Release</text>
            }
        </MudButton>
    </MudStack>
</MudContainer>

@code {
    // 1 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload; the
    // streaming path means the limit caps the request, not in-memory buffering.
    private const long MaxUploadBytes = 1_073_741_824L;

    private List<BatchRowModel> _tracks = new();
    private int _selectedIndex = -1;
    private bool _uploading;
    private int _uploadedCount;
    private string? _errorMessage;

    private IBrowserFile? _selectedImageFile;
    private string? _imagePath;

    private string _albumName = string.Empty;
    private string _artist = string.Empty;
    private string _genre = string.Empty;
    private string _releaseDate = string.Empty;
    private ReleaseType _releaseType = ReleaseType.Single;
    private ReleaseMedium _medium = ReleaseMedium.Cut;

    // Switching to a single-track medium collapses any multi-track selection to the first row so the
    // single-track invariant holds before submit. The predicate reads the same MediumRules cardinality
    // declaration the upload service enforces, so the form and the domain cannot drift.
    private void OnMediumChanged(ReleaseMedium medium)
    {
        _medium = medium;
        if (MediumRules.CardinalityOf(medium).IsSingleTrack && _tracks.Count > 1)
        {
            _tracks.RemoveRange(1, _tracks.Count - 1);
            _selectedIndex = _tracks.Count > 0 ? 0 : -1;
        }
    }

    // Single-track WAV picker for Session/Mix: replaces the one row rather than appending.
    private void HandleSingleWavSelected(InputFileChangeEventArgs e)
    {
        _errorMessage = null;
        var file = e.File;
        if (!file.Name.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
        {
            Snackbar.Add($"'{file.Name}' is not a .wav file.", Severity.Warning);
            return;
        }

        _tracks.Clear();
        _tracks.Add(new BatchRowModel
        {
            WavFile = file,
            TrackName = Path.GetFileNameWithoutExtension(file.Name)
        });
        _selectedIndex = 0;
    }

    private void HandleWavFilesSelected(IReadOnlyList<IBrowserFile> files)
    {
        _errorMessage = null;
        foreach (var file in files)
        {
            if (!file.Name.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
            {
                Snackbar.Add($"Skipped '{file.Name}' — not a .wav file.", Severity.Warning);
                continue;
            }

            _tracks.Add(new BatchRowModel
            {
                WavFile = file,
                TrackName = Path.GetFileNameWithoutExtension(file.Name)
            });
        }

        if (_selectedIndex < 0 && _tracks.Count > 0)
        {
            _selectedIndex = 0;
        }
    }

    private void MoveUp(int i)
    {
        if (i == 0) return;
        (_tracks[i], _tracks[i - 1]) = (_tracks[i - 1], _tracks[i]);
        if (_selectedIndex == i) _selectedIndex = i - 1;
        else if (_selectedIndex == i - 1) _selectedIndex = i;
    }

    private void MoveDown(int i)
    {
        if (i == _tracks.Count - 1) return;
        (_tracks[i], _tracks[i + 1]) = (_tracks[i + 1], _tracks[i]);
        if (_selectedIndex == i) _selectedIndex = i + 1;
        else if (_selectedIndex == i + 1) _selectedIndex = i;
    }

    private void RemoveRow(int i)
    {
        _tracks.RemoveAt(i);
        if (i < _selectedIndex) _selectedIndex--;
        if (_selectedIndex >= _tracks.Count) _selectedIndex = _tracks.Count - 1;
    }

    private async Task SubmitAsync()
    {
        _errorMessage = null;

        if (string.IsNullOrWhiteSpace(_albumName))
        {
            _errorMessage = "Release Name is required.";
            return;
        }

        if (string.IsNullOrWhiteSpace(_artist))
        {
            _errorMessage = "Artist is required.";
            return;
        }

        if (_tracks.Count == 0)
        {
            _errorMessage = "Add at least one track.";
            return;
        }

        if (!string.IsNullOrWhiteSpace(_releaseDate)
            && !DateOnly.TryParseExact(_releaseDate, "yyyy-MM-dd", out _))
        {
            _errorMessage = "Release Date must be in YYYY-MM-DD format.";
            return;
        }

        foreach (var t in _tracks)
        {
            if (t.WavFile is null)
            {
                _errorMessage = $"'{t.TrackName}' has no WAV file selected.";
                return;
            }
        }

        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        var userIdValue = authState.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (!long.TryParse(userIdValue, out var createdByUserId))
        {
            // The page is gated by [Authorize] under the Admin role, so a missing or
            // unparseable id here is a configuration bug, not normal client state.
            Logger.LogError("Authenticated user has no parseable NameIdentifier claim: {Value}", userIdValue);
            _errorMessage = "Your session is missing a valid identifier. Please sign in again.";
            return;
        }

        // For single-track media (Session/Mix) the track name is derived from the Release Name —
        // no separate Track Name input is shown. Sync here so the stored name always matches.
        if (MediumRules.CardinalityOf(_medium).IsSingleTrack && _tracks.Count > 0)
        {
            _tracks[0].TrackName = _albumName;
        }

        _imagePath = null; // Clear any stale uploaded path from a prior partial attempt.
        _uploading = true;
        _uploadedCount = 0;

        try
        {
            // Upload any selected cover art once; abort the submit if it fails so we never
            // create tracks expecting an image that was never stored in the vault.
            if (_selectedImageFile is { } imgFile)
            {
                await using var imgStream = imgFile.OpenReadStream(maxAllowedSize: 50_000_000);
                var imgResult = await CmsTrackService.UploadImageAsync(imgStream, imgFile.Name, imgFile.ContentType);
                if (!imgResult.Success)
                {
                    var imgError = imgResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
                    _errorMessage = $"Image upload failed: {imgError}";
                    return;
                }
                _imagePath = imgResult.Value;
            }

            int succeeded = 0, failed = 0;
            for (int i = 0; i < _tracks.Count; i++)
            {
                var row = _tracks[i];
                var trackNumber = i + 1; // 1-based ordinal from list position

                row.Status = BatchRowStatus.Uploading;
                StateHasChanged();

                try
                {
                    // OpenReadStream streams chunks from the browser via the SignalR circuit; the
                    // service wraps it in StreamContent so the whole file is never materialised in
                    // memory before DeepDrftAPI receives it.
                    await using var wavStream = row.WavFile!.OpenReadStream(MaxUploadBytes);
                    var result = await CmsTrackService.UploadTrackAsync(
                        wavStream,
                        row.WavFile.Name,
                        row.WavFile.ContentType,
                        row.TrackName,
                        _artist,
                        string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
                        string.IsNullOrWhiteSpace(_genre) ? null : _genre,
                        string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
                        row.WavFile.Name,
                        createdByUserId,
                        _releaseType,
                        trackNumber,
                        _medium);

                    if (!result.Success || result.Value is null)
                    {
                        var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
                        row.Status = BatchRowStatus.Failed;
                        row.ErrorMessage = error;
                        failed++;
                        Logger.LogWarning("Batch upload: track '{TrackName}' failed: {Error}", row.TrackName, error);
                    }
                    else
                    {
                        // The upload endpoint does not accept an imagePath, so link the cover art with
                        // a follow-up metadata update — same two-step pattern TrackNew/TrackEdit use.
                        if (_imagePath is { } imgPath)
                        {
                            var linkResult = await CmsTrackService.UpdateAsync(
                                result.Value.Id,
                                row.TrackName,
                                _artist,
                                string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
                                string.IsNullOrWhiteSpace(_genre) ? null : _genre,
                                string.IsNullOrWhiteSpace(_releaseDate) ? null : (DateOnly?)DateOnly.ParseExact(_releaseDate, "yyyy-MM-dd"),
                                imgPath,
                                _releaseType,
                                _medium,
                                trackNumber);

                            if (!linkResult.Success)
                            {
                                // Non-blocking: track is persisted; cover art can be linked via TrackEdit.
                                Logger.LogWarning("Batch upload: cover art link failed for '{TrackName}' (id={Id}) — track uploaded but image unlinked",
                                    row.TrackName, result.Value.Id);
                            }
                        }

                        // Mix uploads fire the server-side high-res waveform trigger (§3.4). The CMS
                        // computes nothing — the API derives the datum from the audio it just stored.
                        // Non-blocking: the track is persisted; a failed trigger is recoverable from
                        // the Mixes browser's per-row Generate action.
                        if (_medium == ReleaseMedium.Mix && result.Value.ReleaseId is { } mixReleaseId)
                        {
                            var waveformResult = await CmsReleaseService.GenerateMixWaveformAsync(mixReleaseId);
                            if (!waveformResult.Success)
                            {
                                Logger.LogWarning("Batch upload: mix waveform trigger failed for release {ReleaseId} ('{TrackName}')",
                                    mixReleaseId, row.TrackName);
                                Snackbar.Add("Mix uploaded, but waveform generation failed. Retry from the Mixes browser.", Severity.Warning);
                            }
                        }

                        row.Status = BatchRowStatus.Done;
                        succeeded++;
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Batch upload: exception uploading '{TrackName}'", row.TrackName);
                    row.Status = BatchRowStatus.Failed;
                    row.ErrorMessage = "Upload failed — please try again.";
                    failed++;
                }

                _uploadedCount++;
                StateHasChanged();
            }

            if (failed == 0)
            {
                Snackbar.Add($"Uploaded {succeeded} track(s).", Severity.Success);
                VM.Invalidate();
                Navigation.NavigateTo("/tracks");
            }
            else
            {
                Snackbar.Add($"Uploaded {succeeded} track(s); {failed} failed — review errors below.", Severity.Warning);
                // Stay on page so the admin can see the failed rows.
            }
        }
        finally
        {
            _uploading = false;
            StateHasChanged();
        }
    }
}
