namespace TeleWave.Domain.Media;

/// <summary>
/// Один видеофайл в библиотеке. После загрузки нормализуется ffmpeg'ом в HLS-сегменты по
/// <see cref="SegmentSeconds"/> секунд; длительность добивается до кратности длине сегмента, чтобы
/// эфирная математика (следующий этап) оставалась целочисленной. Rich model: приватные сеттеры,
/// фабрика <see cref="Register"/> и поведенческие переходы статуса.
/// </summary>
public class MediaAsset
{
    public Guid Id { get; private set; }

    /// <summary>Исходное имя файла (для отображения в админке).</summary>
    public string OriginalFileName { get; private set; } = string.Empty;

    /// <summary>Расширение исходника с точкой, в нижнем регистре (например «.mkv»).</summary>
    public string OriginalExtension { get; private set; } = string.Empty;

    public MediaSource Source { get; private set; }
    public MediaAssetStatus Status { get; private set; }

    /// <summary>Точная длительность нарезанного ассета (кратна <see cref="SegmentSeconds"/>).</summary>
    public TimeSpan? Duration { get; private set; }

    public int? SegmentSeconds { get; private set; }
    public int? SegmentCount { get; private set; }
    public int? Width { get; private set; }
    public int? Height { get; private set; }
    public string? VideoCodec { get; private set; }
    public string? AudioCodec { get; private set; }

    /// <summary>Относительный путь каталога сегментов от корня хранилища («assets/{id}»).</summary>
    public string? RelativePath { get; private set; }

    /// <summary>Текст ошибки, если <see cref="Status"/> == <see cref="MediaAssetStatus.Failed"/>.</summary>
    public string? ErrorMessage { get; private set; }

    /// <summary>Когда началась обработка (переход в Processing) — для расчёта её длительности.</summary>
    public DateTimeOffset? ProcessingStartedAt { get; private set; }

    /// <summary>Сколько заняла обработка завершённого ассета (Ready) = момент готовности − старт обработки.</summary>
    public TimeSpan? ProcessingDuration { get; private set; }

    public DateTimeOffset CreatedAt { get; private set; }
    public DateTimeOffset UpdatedAt { get; private set; }

    private MediaAsset() { }

    public static MediaAsset Register(string originalFileName, string extension, MediaSource source)
    {
        var now = DateTimeOffset.UtcNow;
        return new MediaAsset
        {
            Id = Guid.NewGuid(),
            OriginalFileName = originalFileName,
            OriginalExtension = extension.ToLowerInvariant(),
            Source = source,
            Status = MediaAssetStatus.Pending,
            CreatedAt = now,
            UpdatedAt = now,
        };
    }

    /// <summary>
    /// Ассет, сгенерированный системой (ТВ-заставка). Создаётся в <see cref="MediaAssetStatus.Pending"/>
    /// и нарезается не общим пайплайном обработки, а специализированным фоновым рендерером
    /// (BumperRenderBackgroundService), который по готовности вызывает <see cref="MarkReady"/>.
    /// </summary>
    public static MediaAsset RegisterGenerated(string displayName)
    {
        var now = DateTimeOffset.UtcNow;
        return new MediaAsset
        {
            Id = Guid.NewGuid(),
            OriginalFileName = displayName,
            OriginalExtension = string.Empty,
            Source = MediaSource.Generated,
            Status = MediaAssetStatus.Pending,
            CreatedAt = now,
            UpdatedAt = now,
        };
    }

    public void MarkProcessing()
    {
        Status = MediaAssetStatus.Processing;
        ErrorMessage = null;
        ProcessingStartedAt = DateTimeOffset.UtcNow;
        ProcessingDuration = null;
        Touch();
    }

    public void MarkReady(MediaReadyInfo info)
    {
        Status = MediaAssetStatus.Ready;
        Duration = info.Duration;
        SegmentSeconds = info.SegmentSeconds;
        SegmentCount = info.SegmentCount;
        Width = info.Width;
        Height = info.Height;
        VideoCodec = info.VideoCodec;
        AudioCodec = info.AudioCodec;
        RelativePath = info.RelativePath;
        ErrorMessage = null;
        if (ProcessingStartedAt is { } startedAt)
        {
            var elapsed = DateTimeOffset.UtcNow - startedAt;
            ProcessingDuration = elapsed > TimeSpan.Zero ? elapsed : TimeSpan.Zero;
        }
        Touch();
    }

    public void MarkFailed(string error)
    {
        Status = MediaAssetStatus.Failed;
        ErrorMessage = error;
        Touch();
    }

    /// <summary>Сбросить прерванную обработку (Processing после рестарта) обратно в очередь.</summary>
    public void ResetToPending()
    {
        Status = MediaAssetStatus.Pending;
        ErrorMessage = null;
        ProcessingStartedAt = null;
        Touch();
    }

    private void Touch() => UpdatedAt = DateTimeOffset.UtcNow;
}
