using System.Linq.Expressions;
using DeepDrftData.Repositories;
using DeepDrftModels.DTOs;
using DeepDrftModels.Entities;
using DeepDrftModels.Enums;
using Microsoft.Extensions.Logging;
using Models.Common;
using NetBlocks.Models;

namespace DeepDrftData;

/// <summary>
/// SQL-side release service implementing <see cref="IReleaseService"/>. Deliberately does NOT extend
/// <c>Manager&lt;&gt;</c>: that CRUD base does not fit this read-projection + satellite-write purpose.
/// The layer boundary holds — ReleaseRepository outputs entities, this manager outputs DTOs via
/// TrackConverter, the single authoritative conversion path.
/// </summary>
public class ReleaseManager : IReleaseService
{
    // Distinguishes "release does not exist" from a real failure so the controller can map to 404.
    public const string ReleaseNotFoundMessage = "Release not found.";

    private readonly ReleaseRepository _repository;
    private readonly ILogger<ReleaseManager> _logger;

    public ReleaseManager(ReleaseRepository repository, ILogger<ReleaseManager> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    // Nulls sort to end via the coalescing sentinels, matching TrackManager's convention.
    private static Expression<Func<ReleaseEntity, object>> GetOrderExpression(string? sortColumn)
        => sortColumn switch
        {
            "Title" => r => r.Title,
            "Artist" => r => r.Artist,
            "ReleaseDate" => r => (object)(r.ReleaseDate ?? DateOnly.MaxValue),
            "Medium" => r => r.Medium,
            _ => r => r.Id
        };

    public async Task<ResultContainer<PagedResult<ReleaseDto>>> GetPagedAsync(
        int page, int pageSize, string? sortColumn, bool sortDescending,
        ReleaseMedium? medium, ReleaseFilter? filter = null, CancellationToken cancellationToken = default)
    {
        try
        {
            var parameters = new PagingParameters<ReleaseEntity>
            {
                Page = page,
                PageSize = pageSize,
                OrderBy = GetOrderExpression(sortColumn),
                IsDescending = sortDescending,
            };

            // Collapse an all-null filter to null so the repository skips the predicate block entirely.
            var effectiveFilter = filter is { IsEmpty: false } ? filter : null;
            var entityPage = await _repository.GetPagedByMediumAsync(parameters, medium, effectiveFilter, cancellationToken);

            var releaseIds = entityPage.Items.Select(r => r.Id).ToList();
            var counts = await _repository.GetTrackCountsByReleaseIdsAsync(releaseIds, cancellationToken);

            var dtos = entityPage.Items
                .Select(r =>
                {
                    var dto = TrackConverter.Convert(r);
                    dto.TrackCount = counts.GetValueOrDefault(r.Id);
                    return dto;
                });

            var dtoPage = PagedResult<ReleaseDto>.From(entityPage, dtos);
            return ResultContainer<PagedResult<ReleaseDto>>.CreatePassResult(dtoPage);
        }
        catch (Exception e)
        {
            return ResultContainer<PagedResult<ReleaseDto>>.CreateFailResult(e.Message);
        }
    }

    public async Task<ResultContainer<ReleaseDto?>> GetByIdAsync(long id, CancellationToken cancellationToken = default)
    {
        try
        {
            var entity = await _repository.GetByIdWithMetadataAsync(id, cancellationToken);
            // TrackConverter nulls the non-matching satellite. TrackCount is not loaded for the detail
            // read (the Tracks collection isn't Include'd) and is not needed by detail consumers.
            return ResultContainer<ReleaseDto?>.CreatePassResult(
                entity is null ? null : TrackConverter.Convert(entity));
        }
        catch (Exception e)
        {
            return ResultContainer<ReleaseDto?>.CreateFailResult(e.Message);
        }
    }

    public async Task<ResultContainer<ReleaseDto?>> GetByEntryKeyAsync(string entryKey, CancellationToken cancellationToken = default)
    {
        try
        {
            var entity = await _repository.GetByEntryKeyWithMetadataAsync(entryKey, cancellationToken);
            return ResultContainer<ReleaseDto?>.CreatePassResult(
                entity is null ? null : TrackConverter.Convert(entity));
        }
        catch (Exception e)
        {
            return ResultContainer<ReleaseDto?>.CreateFailResult(e.Message);
        }
    }

    public async Task<ResultContainer<List<string>>> GetTrackEntryKeysAsync(long releaseId, CancellationToken cancellationToken = default)
    {
        try
        {
            var keys = await _repository.GetTrackEntryKeysByReleaseIdAsync(releaseId, cancellationToken);
            return ResultContainer<List<string>>.CreatePassResult(keys);
        }
        catch (Exception e)
        {
            return ResultContainer<List<string>>.CreateFailResult(e.Message);
        }
    }

    public async Task<Result> SetSessionHeroImageAsync(long releaseId, string heroImageEntryKey, CancellationToken cancellationToken = default)
    {
        try
        {
            var release = await _repository.GetByIdWithMetadataAsync(releaseId, cancellationToken);
            if (release is null)
                return Result.CreateFailResult(ReleaseNotFoundMessage);

            if (release.Medium != ReleaseMedium.Session)
                return Result.CreateFailResult($"Release {releaseId} is not a Session medium.");

            await _repository.SetHeroImageEntryKeyAsync(releaseId, heroImageEntryKey, cancellationToken);
            return Result.CreatePassResult();
        }
        catch (Exception e)
        {
            return Result.CreateFailResult(e.Message);
        }
    }

    public async Task<Result> SetMixWaveformAsync(long releaseId, string waveformEntryKey, CancellationToken cancellationToken = default)
    {
        try
        {
            var release = await _repository.GetByIdWithMetadataAsync(releaseId, cancellationToken);
            if (release is null)
                return Result.CreateFailResult(ReleaseNotFoundMessage);

            if (release.Medium != ReleaseMedium.Mix)
                return Result.CreateFailResult($"Release {releaseId} is not a Mix medium.");

            await _repository.SetWaveformEntryKeyAsync(releaseId, waveformEntryKey, cancellationToken);
            return Result.CreatePassResult();
        }
        catch (Exception e)
        {
            return Result.CreateFailResult(e.Message);
        }
    }
}
