using Microsoft.Extensions.Options;
using TeleWave.Domain.Media;
using TeleWave.Infrastructure.Media;
using Xunit;

namespace TeleWave.Integration.Tests;

/// <summary>
/// Файловая часть ручного разбора: перечисление, перенос и подчистка спутников. Работает с реальной
/// ФС во временном каталоге — код удаляет файлы, и проверять его на моках бессмысленно.
/// </summary>
public sealed class ManualInboxStorageTests : IDisposable
{
    private readonly string _root = Path.Combine(
        Path.GetTempPath(),
        "telewave-tests-" + Guid.NewGuid().ToString("N")[..8]
    );

    private readonly FileSystemMediaStorage _storage;
    private readonly MediaPathResolver _paths;

    public ManualInboxStorageTests()
    {
        _paths = new MediaPathResolver(Options.Create(new StorageOptions { RootPath = _root }));
        _paths.EnsureDirectories();
        _storage = new FileSystemMediaStorage(_paths);
    }

    [Fact]
    public void ListManualInbox_WalksSubdirectories_AndReturnsRelativePaths()
    {
        Write("Сериал/s01e01.mkv");
        Write("Сериал/s01e02.mkv");
        Write("одиночный.mp4");

        var files = _storage.ListManualInbox(int.MaxValue);

        Assert.Equal(3, files.Count);
        // Разделитель нормализован: путь уезжает в URL и возвращается строкой запроса.
        Assert.Contains(files, f => f.RelativePath == "Сериал/s01e01.mkv");
        Assert.Contains(files, f => f.RelativePath == "одиночный.mp4");
        Assert.All(files, f => Assert.True(f.SizeBytes > 0));
    }

    [Fact]
    public async Task PromoteToOriginal_MovesFileOutOfManual()
    {
        Write("Сериал/s01e01.mkv");
        var assetId = Guid.NewGuid();

        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "Сериал/s01e01.mkv",
            assetId,
            ".mkv",
            default
        );

        Assert.False(File.Exists(Path.Combine(_paths.ManualDir, "Сериал", "s01e01.mkv")));
        Assert.True(File.Exists(_paths.OriginalPath(assetId, ".mkv")));
    }

    [Fact]
    public async Task Cleanup_RemovesSiblings_ButKeepsOtherVideos()
    {
        Write("Сериал/s01e01.mkv");
        Write("Сериал/s01e01.srt");
        Write("Сериал/s01e01.ru.srt");
        Write("Сериал/s01e01.nfo");
        Write("Сериал/s01e01.Extended.mkv");
        Write("Сериал/s01e02.mkv");
        Write("Сериал/s01e02.srt");

        // Так делает импорт: сначала файл уходит в originals/, потом подчищаются спутники.
        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "Сериал/s01e01.mkv",
            Guid.NewGuid(),
            ".mkv",
            default
        );
        await _storage.CleanupManualLeftoversAsync("Сериал/s01e01.mkv", default);

        var left = Directory
            .EnumerateFiles(Path.Combine(_paths.ManualDir, "Сериал"))
            .Select(Path.GetFileName)
            .ToList();

        // Субтитры и nfo забранной серии ушли…
        Assert.DoesNotContain("s01e01.srt", left);
        Assert.DoesNotContain("s01e01.ru.srt", left);
        Assert.DoesNotContain("s01e01.nfo", left);
        // …а другой материал и спутники соседних серий остались.
        Assert.Contains("s01e01.Extended.mkv", left);
        Assert.Contains("s01e02.mkv", left);
        Assert.Contains("s01e02.srt", left);
    }

    [Fact]
    public async Task Cleanup_RemovesEmptiedFolder_ButNotManualRoot()
    {
        Write("Сериал/один.mkv");
        Write("Сериал/один.srt");
        Write("в-корне.mkv");

        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "Сериал/один.mkv",
            Guid.NewGuid(),
            ".mkv",
            default
        );
        await _storage.CleanupManualLeftoversAsync("Сериал/один.mkv", default);

        Assert.False(Directory.Exists(Path.Combine(_paths.ManualDir, "Сериал")));

        // Файл в корне забираем — сам корень manual/ остаться обязан.
        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "в-корне.mkv",
            Guid.NewGuid(),
            ".mkv",
            default
        );
        await _storage.CleanupManualLeftoversAsync("в-корне.mkv", default);

        Assert.True(Directory.Exists(_paths.ManualDir));
    }

    [Fact]
    public async Task Cleanup_RemovesSubtitlesFromNestedFolders()
    {
        // Раскладка из жизни: субтитры лежат не рядом с серией, а в подпапках по языкам, и названы
        // иначе — совпадает только номер серии.
        Write("Season 16/S16E21.mkv");
        Write("Season 16/subs_ru/S16E21 - The Father, the Son.srt");
        Write("Season 16/subs_en/S16E21 - The Father, the Son.srt");
        Write("Season 16/subs_ru/S16E20 - Home Away From Homer.srt");
        Write("Season 16/extras/S16E21 удалённые сцены.mkv");

        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "Season 16/S16E21.mkv",
            Guid.NewGuid(),
            ".mkv",
            default
        );
        await _storage.CleanupManualLeftoversAsync("Season 16/S16E21.mkv", default);

        var season = Path.Combine(_paths.ManualDir, "Season 16");
        Assert.False(
            File.Exists(Path.Combine(season, "subs_ru", "S16E21 - The Father, the Son.srt"))
        );
        Assert.False(
            File.Exists(Path.Combine(season, "subs_en", "S16E21 - The Father, the Son.srt"))
        );
        // Субтитры соседней серии остаются: её ещё не забирали.
        Assert.True(
            File.Exists(Path.Combine(season, "subs_ru", "S16E20 - Home Away From Homer.srt"))
        );
        // Видео не трогаем нигде, даже с тем же номером серии, — это другой материал.
        Assert.True(File.Exists(Path.Combine(season, "extras", "S16E21 удалённые сцены.mkv")));
    }

    [Fact]
    public async Task Cleanup_RemovesEmptiedNestedFolders_AfterLastEpisode()
    {
        Write("Season 16/S16E01.mkv");
        Write("Season 16/S16E02.mkv");
        Write("Season 16/subs_ru/S16E01 - Treehouse.srt");
        Write("Season 16/subs_ru/S16E02 - All's Fair.srt");

        foreach (var episode in new[] { "S16E01", "S16E02" })
        {
            await _storage.PromoteToOriginalAsync(
                MediaSource.ManualInbox,
                $"Season 16/{episode}.mkv",
                Guid.NewGuid(),
                ".mkv",
                default
            );
            await _storage.CleanupManualLeftoversAsync($"Season 16/{episode}.mkv", default);
        }

        // Опустевшие подпапки уходят вместе с каталогом сезона — корень manual/ остаётся.
        Assert.False(Directory.Exists(Path.Combine(_paths.ManualDir, "Season 16")));
        Assert.True(Directory.Exists(_paths.ManualDir));
    }

    [Fact]
    public async Task Cleanup_KeepsFolderThatStillHasFiles()
    {
        Write("Сериал/s01e01.mkv");
        Write("Сериал/s01e02.mkv");

        await _storage.PromoteToOriginalAsync(
            MediaSource.ManualInbox,
            "Сериал/s01e01.mkv",
            Guid.NewGuid(),
            ".mkv",
            default
        );
        await _storage.CleanupManualLeftoversAsync("Сериал/s01e01.mkv", default);

        Assert.True(File.Exists(Path.Combine(_paths.ManualDir, "Сериал", "s01e02.mkv")));
    }

    private void Write(string relativePath)
    {
        var full = Path.Combine(
            _paths.ManualDir,
            relativePath.Replace('/', Path.DirectorySeparatorChar)
        );
        Directory.CreateDirectory(Path.GetDirectoryName(full)!);
        File.WriteAllText(full, "x");
    }

    public void Dispose()
    {
        if (Directory.Exists(_root))
            Directory.Delete(_root, recursive: true);
    }
}
