namespace TeleWave.Domain.Library;

/// <summary>
/// Переиспользуемое шоу в общей библиотеке: сериал (упорядоченные серии) либо полнометражка.
/// Серии идут строго в порядке <see cref="ShowEpisode.Position"/>; где остановился показ — знает
/// состояние слота планировщика (<c>Programming/SlotState</c>), а не само шоу: одно шоу играет
/// на нескольких каналах и в нескольких слотах, и курсор у каждого свой.
/// </summary>
public class Show
{
    private readonly List<ShowEpisode> _episodes = [];
    private readonly List<ShowGenre> _genres = [];

    public Guid Id { get; private set; }
    public string Name { get; private set; } = string.Empty;

    /// <summary>Оригинальное название (обычно на английском) — по нему ищутся метаданные; на экранах
    /// продолжаем показывать <see cref="Name"/>. Null/пусто — ищем по <see cref="Name"/>.</summary>
    public string? OriginalName { get; private set; }

    public string? Description { get; private set; }
    public ShowKind Kind { get; private set; }

    /// <summary>Возрастной рейтинг (MPAA) или null, если не проставлен ни источником, ни вручную.
    /// Шоу без рейтинга планировщик не отсекает: неизвестное не значит «взрослое».</summary>
    public ShowAudience? Audience { get; private set; }

    public DateTimeOffset CreatedAt { get; private set; }

    // ── Метаданные (TMDb/OMDb/вручную) ──
    /// <summary>Источник метаданных: «tmdb»/«omdb»/«manual» или null, если не заданы.</summary>
    public string? MetadataProvider { get; private set; }

    /// <summary>Идентификатор шоу во внешнем источнике (для довыгрузки серий).</summary>
    public string? MetadataExternalId { get; private set; }

    public int? Year { get; private set; }

    /// <summary>Постер шоу — ссылка на запись общего реестра изображений (<c>Domain/Images</c>) или null.</summary>
    public Guid? PosterImageId { get; private set; }

    /// <summary>Серии шоу (backing-field для EF). Порядок показа — по <see cref="ShowEpisode.Position"/>;
    /// потребители сортируют явно (см. загрузчик планировщика).</summary>
    public IReadOnlyList<ShowEpisode> Episodes => _episodes;

    /// <summary>Жанры шоу (backing-field для EF). Ровно один помечен основным, если список не пуст.</summary>
    public IReadOnlyList<ShowGenre> Genres => _genres;

    private Show() { }

    public static Show Create(
        string name,
        ShowKind kind,
        string? description = null,
        string? originalName = null,
        ShowAudience? audience = null
    ) =>
        new()
        {
            Id = Guid.NewGuid(),
            Name = name,
            OriginalName = Normalize(originalName),
            Kind = kind,
            Description = description,
            Audience = audience,
            CreatedAt = DateTimeOffset.UtcNow,
        };

    /// <summary>Задать возрастной рейтинг; null — снять (вернуть в «не проставлен»).</summary>
    public void SetAudience(ShowAudience? audience) => Audience = audience;

    /// <summary>Основной жанр или null, если жанры не проставлены.</summary>
    public Guid? PrimaryGenreId => _genres.FirstOrDefault(g => g.IsPrimary)?.GenreId;

    /// <summary>
    /// Полностью заменяет набор жанров; дубликаты и пустые идентификаторы отбрасываются. Основным
    /// становится <paramref name="primaryGenreId"/>, если он попал в набор, иначе первый в списке —
    /// так шоу с жанрами никогда не остаётся без основного.
    /// </summary>
    public void SetGenres(IEnumerable<Guid> genreIds, Guid? primaryGenreId = null)
    {
        var ids = genreIds.Where(id => id != Guid.Empty).Distinct().ToList();
        _genres.Clear();
        if (ids.Count == 0)
            return;

        var primary =
            primaryGenreId is { } candidate && ids.Contains(candidate) ? candidate : ids[0];
        foreach (var id in ids)
            _genres.Add(ShowGenre.Create(Id, id, id == primary));
    }

    public void Rename(string name, string? description)
    {
        Name = name;
        Description = description;
    }

    /// <summary>Изменить отображаемое название (на экранах). Метаданные ищутся по <see cref="OriginalName"/>.</summary>
    public void SetName(string name) => Name = name;

    /// <summary>Задать/снять оригинальное название (пустая строка трактуется как отсутствие).</summary>
    public void SetOriginalName(string? originalName) => OriginalName = Normalize(originalName);

    private static string? Normalize(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    /// <summary>Добавляет серию в конец. Для <see cref="ShowKind.Single"/> допустима ровно одна серия
    /// (инвариант защищён самим агрегатом; вызывающий обычно проверяет <see cref="CanAddEpisode"/> заранее
    /// и возвращает управляемую ошибку — исключение здесь лишь страховка от обхода).</summary>
    public ShowEpisode AddEpisode(Guid mediaAssetId)
    {
        if (!CanAddEpisode)
            throw new InvalidOperationException(
                "Только сериал может содержать больше одной серии."
            );

        var nextPosition = _episodes.Count == 0 ? 0 : _episodes.Max(e => e.Position) + 1;
        var episode = ShowEpisode.Create(Id, mediaAssetId, nextPosition);
        _episodes.Add(episode);
        return episode;
    }

    public bool RemoveEpisode(Guid episodeId)
    {
        var episode = _episodes.FirstOrDefault(e => e.Id == episodeId);
        if (episode is null)
            return false;
        _episodes.Remove(episode);
        return true;
    }

    /// <summary>Несколько серий бывает только у сериала. У полнометражки и у ролика-врезки серия ровно
    /// одна: ролик с тремя сериями вёл бы себя в планировщике как мини-сериал, а задуман как единица.</summary>
    public bool CanAddEpisode => Kind == ShowKind.Series || _episodes.Count == 0;

    /// <summary>Применить метаданные из внешнего источника. Постер (уже зарегистрирован в реестре) может быть null.</summary>
    /// <summary>Франшиза шоу по данным источника: её идентификатор и название.</summary>
    public string? FranchiseExternalId { get; private set; }

    public string? FranchiseName { get; private set; }

    public void ApplyMetadata(
        string provider,
        string externalId,
        string? description,
        int? year,
        Guid? posterImageId,
        MetadataFranchise? franchise = null
    )
    {
        MetadataProvider = provider;
        MetadataExternalId = externalId;
        // Франшизу запоминаем, а не забываем сразу после применения: по ней потом предлагаются
        // коллекции, и перезапрашивать источник ради каждого фильма было бы расточительно.
        // Отсутствие в ответе — не повод стирать известное: у OMDb этих данных нет вовсе.
        if (franchise is not null)
        {
            FranchiseExternalId = franchise.ExternalId;
            FranchiseName = franchise.Name;
        }
        if (!string.IsNullOrWhiteSpace(description))
            Description = description;
        Year = year;
        if (posterImageId is not null)
            PosterImageId = posterImageId;
    }

    /// <summary>Ручная правка метаданных (без внешнего источника).</summary>
    public void UpdateMetadataManual(string? description, int? year)
    {
        MetadataProvider = "manual";
        MetadataExternalId = null;
        Description = description;
        Year = year;
    }

    /// <summary>Привязать/снять постер шоу (ссылка на запись реестра изображений).</summary>
    public void SetPosterImage(Guid? imageId) => PosterImageId = imageId;

    /// <summary>Сбросить все метаданные и отвязать постер (сама картинка остаётся в галерее).</summary>
    public void ClearMetadata()
    {
        MetadataProvider = null;
        MetadataExternalId = null;
        Year = null;
        PosterImageId = null;
        Description = null;
    }
}
