using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Domain.Media;

namespace TeleWave.Application.Media.ManualInbox;

/// <summary>
/// Забирает файл из <c>manual/</c> в библиотеку: проверки, регистрация ассета и перенос в
/// originals/. Общее для обоих разборов — серии в шоу и фильмы пачкой: правила «что можно взять»
/// и порядок «сначала строка в БД, потом файл» обязаны быть одни, иначе один разбор однажды
/// оставит на диске то, что другой убирает.
/// </summary>
public sealed class ManualInboxTaker(IAppDbContext dbContext, IMediaStorage storage)
{
    /// <summary>Что лежит в каталоге, по относительному пути — по нему файл и опознаётся.</summary>
    public IReadOnlyDictionary<string, IMediaStorage.ManualInboxFile> List() =>
        storage
            .ListManualInbox(int.MaxValue)
            .ToDictionary(f => f.RelativePath, StringComparer.Ordinal);

    /// <summary>
    /// Берёт файл: проверяет формат и дубль по имени, регистрирует ассет и переносит его в
    /// originals/. Ошибка возвращается значением, а не исключением: в пакетном разборе одна
    /// негодная строка не должна отменять остальные.
    /// </summary>
    public async Task<Result<Guid>> TakeAsync(
        IReadOnlyDictionary<string, IMediaStorage.ManualInboxFile> available,
        string relativePath,
        CancellationToken cancellationToken
    )
    {
        if (!available.TryGetValue(relativePath, out var file))
            return Result.Failure<Guid>(MediaErrors.SourceNotFound);

        if (!MediaFormats.IsAllowed(file.Name))
            return Result.Failure<Guid>(MediaErrors.UnsupportedFormat);

        var duplicate = await dbContext.MediaAssets.AnyAsync(
            a => a.OriginalFileName == file.Name && a.Status != MediaAssetStatus.Failed,
            cancellationToken
        );
        if (duplicate)
            return Result.Failure<Guid>(MediaErrors.DuplicateFileName);

        var extension = Path.GetExtension(file.Name).ToLowerInvariant();
        var asset = MediaAsset.Register(file.Name, extension, MediaSource.ManualInbox);

        // Порядок жёсткий: сначала строка в БД, затем файл — при сбое переноса регистрация
        // откатывается, чтобы не осталось записи без исходника.
        dbContext.MediaAssets.Add(asset);
        await dbContext.SaveChangesAsync(cancellationToken);
        try
        {
            await storage.PromoteToOriginalAsync(
                MediaSource.ManualInbox,
                relativePath,
                asset.Id,
                extension,
                cancellationToken
            );
        }
        catch (IOException)
        {
            dbContext.MediaAssets.Remove(asset);
            await dbContext.SaveChangesAsync(cancellationToken);
            return Result.Failure<Guid>(MediaErrors.SourceNotFound);
        }

        return Result.Success(asset.Id);
    }

    /// <summary>
    /// Убирает то, что осталось в каталоге от забранного файла: спутники (субтитры, nfo, обложка)
    /// и опустевшую папку. Без основного файла они мусор, за которым никто не вернётся.
    /// </summary>
    public Task CleanupAsync(string relativePath, CancellationToken cancellationToken) =>
        storage.CleanupManualLeftoversAsync(relativePath, cancellationToken);
}
