using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Client.ViewModels;
using Models.Common;
using NetBlocks.Models;

namespace DeepDrftTests;

/// <summary>
/// Unit tests for the release-embed track resolution (<see cref="FramePlayerViewModel"/>). The VM is
/// the data half of FramePlayer's release path: resolve a release EntryKey to its ordered track list,
/// which the page then stages (track 0) and arms into the queue. It is pure async logic over the two
/// data-service seams, so it is exercised here against recording fakes — no browser, no JS, no HTTP.
/// Coverage: ordered non-empty resolution, single-track release, release-not-found, and a release
/// that resolves to no tracks (the "leave the bar idle, don't throw" path).
/// </summary>
[TestFixture]
public class FramePlayerReleaseResolutionTests
{
    private FakeReleaseData _releaseData = null!;
    private FakeTrackData _trackData = null!;
    private FramePlayerViewModel _vm = null!;

    private const string ReleaseKey = "release-key-1";
    private const long ReleaseId = 42;

    [SetUp]
    public void SetUp()
    {
        _releaseData = new FakeReleaseData();
        _trackData = new FakeTrackData();
        _vm = new FramePlayerViewModel(_releaseData, _trackData);
    }

    private static ReleaseDto Release() => new() { Id = ReleaseId, EntryKey = ReleaseKey, Title = "Album" };

    private static List<TrackDto> Tracks(int count) =>
        Enumerable.Range(1, count)
            .Select(i => new TrackDto { EntryKey = $"track-{i}", TrackName = $"Track {i}", TrackNumber = i })
            .ToList();

    [Test]
    public async Task Load_ResolvesOrderedNonEmptyTrackList_AndQueriesByResolvedReleaseId()
    {
        _releaseData.Release = Release();
        _trackData.Page = Tracks(3);

        await _vm.Load(ReleaseKey);

        Assert.Multiple(() =>
        {
            Assert.That(_vm.Tracks.Select(t => t.EntryKey),
                Is.EqualTo(new[] { "track-1", "track-2", "track-3" }));
            // The track page must be narrowed by the resolved release.Id (the int FK join), sorted by
            // the explicit TrackNumber ordinal — the same resolution the Cut detail page uses.
            Assert.That(_trackData.LastReleaseId, Is.EqualTo(ReleaseId));
            Assert.That(_trackData.LastSortColumn, Is.EqualTo("TrackNumber"));
        });
    }

    [Test]
    public async Task Load_WithSingleTrackRelease_ResolvesAOneItemList()
    {
        _releaseData.Release = Release();
        _trackData.Page = Tracks(1);

        await _vm.Load(ReleaseKey);

        Assert.That(_vm.Tracks, Has.Count.EqualTo(1));
        Assert.That(_vm.Tracks[0].EntryKey, Is.EqualTo("track-1"));
    }

    [Test]
    public async Task Load_WhenReleaseNotFound_LeavesTracksEmptyAndDoesNotQueryTracks()
    {
        _releaseData.Release = null; // GetByEntryKey returns a fail result.

        await _vm.Load(ReleaseKey);

        Assert.Multiple(() =>
        {
            Assert.That(_vm.Tracks, Is.Empty);
            Assert.That(_trackData.WasQueried, Is.False);
        });
    }

    [Test]
    public async Task Load_WhenReleaseResolvesToNoTracks_LeavesTracksEmptyWithoutThrowing()
    {
        _releaseData.Release = Release();
        _trackData.Page = new List<TrackDto>(); // empty page

        await _vm.Load(ReleaseKey);

        Assert.That(_vm.Tracks, Is.Empty);
    }

    [Test]
    public async Task Load_ResetsTracksFromAPriorLoad()
    {
        _releaseData.Release = Release();
        _trackData.Page = Tracks(3);
        await _vm.Load(ReleaseKey);
        Assert.That(_vm.Tracks, Has.Count.EqualTo(3));

        // A second load whose release is not found must not retain the prior album's tracks.
        _releaseData.Release = null;
        await _vm.Load("another-key");

        Assert.That(_vm.Tracks, Is.Empty);
    }

    private sealed class FakeReleaseData : IReleaseDataService
    {
        public ReleaseDto? Release { get; set; }

        public Task<ApiResult<ReleaseDto>> GetByEntryKey(string entryKey)
            => Task.FromResult(Release is null
                ? ApiResult<ReleaseDto>.CreateFailResult("not found")
                : ApiResult<ReleaseDto>.CreatePassResult(Release));

        public Task<ApiResult<PagedResult<ReleaseDto>>> GetPaged(
            string? medium, int page, int pageSize, string? sortColumn = null,
            bool sortDescending = false, string? search = null, string? genre = null)
            => throw new NotSupportedException("FramePlayerViewModel does not page releases.");
    }

    private sealed class FakeTrackData : ITrackDataService
    {
        public List<TrackDto> Page { get; set; } = new();
        public bool WasQueried { get; private set; }
        public long? LastReleaseId { get; private set; }
        public string? LastSortColumn { get; private set; }

        public Task<ApiResult<PagedResult<TrackDto>>> GetPage(
            int pageNumber, int pageSize, string? sortColumn = null, bool sortDescending = false,
            string? searchText = null, string? album = null, string? genre = null, long? releaseId = null)
        {
            WasQueried = true;
            LastReleaseId = releaseId;
            LastSortColumn = sortColumn;
            var paged = new PagedResult<TrackDto>
            {
                Items = Page,
                TotalCount = Page.Count,
                Page = pageNumber,
                PageSize = pageSize,
            };
            return Task.FromResult(ApiResult<PagedResult<TrackDto>>.CreatePassResult(paged));
        }

        // Inert remainder — FramePlayerViewModel only calls GetPage.
        public Task<ApiResult<List<ReleaseDto>>> GetAlbums() => throw new NotSupportedException();
        public Task<ApiResult<List<GenreSummaryDto>>> GetGenres() => throw new NotSupportedException();
        public Task<ApiResult<TrackDto>> GetTrack(string trackId) => throw new NotSupportedException();
        public Task<ApiResult<WaveformProfileDto?>> GetTrackWaveform(string trackEntryKey) => throw new NotSupportedException();
        public Task<ApiResult<TrackDto?>> GetRandomTrack() => throw new NotSupportedException();
    }
}
