using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TeleWave.Domain.Library;
using TeleWave.Infrastructure.Persistence;

namespace TeleWave.Infrastructure.Library;

/// <summary>
/// Идемпотентный сидинг справочника жанров. Жанр опознаётся по <see cref="Genre.Slug"/>, поэтому
/// переименование администратором сид переживает; недостающие псевдонимы дописываются и к уже
/// существующим жанрам — так новые варианты написания приезжают с обновлением приложения.
///
/// Псевдоним, уже занятый другим жанром, пропускается: уникальность псевдонима — условие
/// однозначного сопоставления метаданных, и ручная правка администратора имеет приоритет над сидом.
/// </summary>
public sealed class GenreSeeder(AppDbContext dbContext, ILogger<GenreSeeder> logger)
{
    private sealed record GenreSeed(string Slug, string Name, string[] Aliases);

    // Порядок массива задаёт SortOrder — так список в UI выглядит осмысленно, а не по алфавиту.
    // В псевдонимах: идентификаторы TMDb (точное сопоставление) и имена от TMDb/OMDb (запасное).
    private static readonly GenreSeed[] Seeds =
    [
        new("action", "Боевик", ["tmdb:28", "tmdb:10759", "action", "action & adventure"]),
        new("adventure", "Приключения", ["tmdb:12", "adventure"]),
        new("animation", "Мультфильм", ["tmdb:16", "animation", "анимация"]),
        new("comedy", "Комедия", ["tmdb:35", "comedy"]),
        new("crime", "Криминал", ["tmdb:80", "crime"]),
        new("drama", "Драма", ["tmdb:18", "drama"]),
        new("thriller", "Триллер", ["tmdb:53", "thriller"]),
        new("horror", "Ужасы", ["tmdb:27", "horror"]),
        new("sci-fi", "Фантастика", ["tmdb:878", "tmdb:10765", "science fiction", "sci-fi"]),
        new("fantasy", "Фэнтези", ["tmdb:14", "fantasy"]),
        new("mystery", "Детектив", ["tmdb:9648", "mystery"]),
        new("romance", "Мелодрама", ["tmdb:10749", "romance", "романтика"]),
        new("family", "Семейный", ["tmdb:10751", "family"]),
        new("kids", "Детский", ["tmdb:10762", "kids"]),
        new("music", "Музыкальный", ["tmdb:10402", "music", "musical"]),
        new("history", "Исторический", ["tmdb:36", "history"]),
        new("war", "Военный", ["tmdb:10752", "tmdb:10768", "war", "war & politics"]),
        new("western", "Вестерн", ["tmdb:37", "western"]),
        new("documentary", "Документальный", ["tmdb:99", "documentary"]),
        new("biography", "Биография", ["biography"]),
        new("sport", "Спорт", ["sport"]),
        new("news", "Новости", ["tmdb:10763", "news"]),
        new("reality", "Реалити", ["tmdb:10764", "reality"]),
        new("talk-show", "Ток-шоу", ["tmdb:10767", "talk", "talk-show"]),
        new("soap", "Мыльная опера", ["tmdb:10766", "soap"]),
        new("tv-movie", "Телефильм", ["tmdb:10770", "tv movie"]),
        new("short", "Короткометражка", ["short"]),
    ];

    public async Task SeedAsync(CancellationToken cancellationToken = default)
    {
        var existing = await dbContext
            .Genres.Include(g => g.Aliases)
            .ToDictionaryAsync(g => g.Slug, cancellationToken);

        var takenAliases = existing
            .Values.SelectMany(g => g.Aliases.Select(a => new { a.Value, g.Slug }))
            .ToDictionary(x => x.Value, x => x.Slug);

        var createdGenres = 0;
        var createdAliases = 0;

        for (var i = 0; i < Seeds.Length; i++)
        {
            var seed = Seeds[i];
            if (!existing.TryGetValue(seed.Slug, out var genre))
            {
                genre = Genre.Create(seed.Name, seed.Slug, i, isSystem: true);
                dbContext.Genres.Add(genre);
                existing[seed.Slug] = genre;
                createdGenres++;
            }

            createdAliases += AddAliases(genre, seed, takenAliases);
        }

        if (createdGenres == 0 && createdAliases == 0)
            return;

        await dbContext.SaveChangesAsync(cancellationToken);
        logger.LogInformation(
            "Seeded genres: {Genres} new, {Aliases} new aliases",
            createdGenres,
            createdAliases
        );
    }

    /// <summary>
    /// Дописывает жанру недостающие варианты написания. Само название и slug тоже годятся —
    /// провайдеры отдают то одно, то другое. Занятый другим жанром вариант пропускается: один
    /// и тот же псевдоним не может вести в два жанра, иначе сопоставление стало бы недетерминированным.
    /// Возвращает число добавленных.
    /// </summary>
    private int AddAliases(Genre genre, GenreSeed seed, Dictionary<string, string> takenAliases)
    {
        var added = 0;

        foreach (var alias in seed.Aliases.Append(seed.Slug).Append(seed.Name))
        {
            var normalized = GenreAlias.Normalize(alias);
            if (normalized.Length == 0)
                continue;

            if (takenAliases.TryGetValue(normalized, out var owner))
            {
                if (owner != seed.Slug)
                    logger.LogDebug(
                        "Genre alias '{Alias}' already belongs to '{Owner}' — skipped",
                        normalized,
                        owner
                    );
                continue;
            }

            genre.AddAlias(normalized);
            takenAliases[normalized] = seed.Slug;
            added++;
        }

        return added;
    }
}
