using System.Text.Json;
using System.Text.Json.Serialization;
using TeleWave.Domain.Library;

namespace TeleWave.Application.Programming.Templates;

/// <summary>
/// Окно детского времени: с <paramref name="From"/> до <paramref name="To"/> во времени канала
/// в эфир идёт только контент не строже <paramref name="MaxAudience"/>. Окно может переходить
/// через полночь (23:00–06:00) — тогда границы сравниваются в обратную сторону.
/// </summary>
public sealed record AudienceWindow(TimeOnly From, TimeOnly To, ShowAudience MaxAudience)
{
    public bool Contains(TimeOnly moment) =>
        From <= To ? moment >= From && moment < To : moment >= From || moment < To;
}

/// <summary>
/// Правила отбора кандидатов на уровне канала (см. 3.8). Это жёсткие фильтры: они отсекают
/// недопустимое до жребия, поэтому не требуют пересборки и не ломают воспроизводимость.
///
/// Область действия — весь канал: дейпарты сюда не заведены намеренно, окна детского времени
/// и так задаются временем, а потолок повторов осмыслен только целиком по каналу.
/// </summary>
public sealed record PlanningRules(
    IReadOnlyList<AudienceWindow>? MaxAudienceByTime = null,
    /// <summary>Не чаще <c>Max</c> раз за <c>WindowDays</c> суток (null — без ограничения).</summary>
    RepeatLimitRule? MaxRepeatsInWindow = null,
    // ── Пост-проверки: считаются по готовой ленте, дают предупреждения и ничего не переигрывают. ──
    /// <summary>Потолок врезок в часе, минуты (null — не проверять).</summary>
    int? MaxBreakMinutesPerHour = null,
    /// <summary>Потолок доли одного жанра за вещательные сутки, проценты (null — не проверять).</summary>
    int? MaxGenreSharePercent = null,
    /// <summary>Потолок доли эфира, отданной фону, проценты (null — не проверять).</summary>
    int? MaxFallbackSharePercent = null
)
{
    private static readonly JsonSerializerOptions Options = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        Converters = { new JsonStringEnumConverter() },
    };

    /// <summary>Возрастной потолок в указанный момент времени канала (null — без ограничения).</summary>
    public ShowAudience? AudienceAt(TimeOnly moment)
    {
        if (MaxAudienceByTime is not { Count: > 0 } windows)
            return null;

        // Пересекающиеся окна разрешаются в пользу строгого: детское время не должно
        // отменяться более широким окном, случайно наложенным сверху.
        var applicable = windows.Where(w => w.Contains(moment)).Select(w => w.MaxAudience).ToList();
        return applicable.Count > 0 ? applicable.Min() : null;
    }

    public string ToJson() => JsonSerializer.Serialize(this, Options);

    public static PlanningRules? FromJson(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
            return null;

        try
        {
            return JsonSerializer.Deserialize<PlanningRules>(json, Options);
        }
        catch (JsonException)
        {
            return null;
        }
    }
}

public sealed record RepeatLimitRule(int WindowDays, int Max);
