#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// A versioned, trusted projection of the operator-owned VI file. Browser playlist
/// payloads carry only <see cref="TrustedViManualReference.SubjectSentinel"/> and
/// <see cref="RowVersion"/>; stock names are resolved again inside the native boundary.
/// </summary>
public sealed record TrustedViStockNameSnapshot(
    IReadOnlyList<string> StockNames,
    string RowVersion);

public interface ITrustedViStockNameSnapshotSource
{
    Task<TrustedViStockNameSnapshot> ReadStockNameSnapshotAsync(
        CancellationToken cancellationToken = default);
}

public static class TrustedViManualReference
{
    public const string SubjectSentinel = "@MBN_VI_SNAPSHOT_V1";
    public const int RowVersionLength = 64;
    public const int MaximumItemCount = 100;

    public static bool IsRowVersion(string? value) =>
        value is { Length: RowVersionLength } &&
        value.All(character =>
            character is >= '0' and <= '9' or >= 'a' and <= 'f');

    /// <summary>
    /// Resolves a PAGED_VI selection. Versioned selections fail closed unless the
    /// current trusted snapshot is byte-for-byte the version selected by the operator.
    /// Historical comma-name selections remain available only with an empty data code.
    /// </summary>
    public static async Task<ViPagedQuoteLoadRequest> ResolveAsync(
        LegacySceneSelection selection,
        int pageIndexZeroBased,
        ITrustedViStockNameSnapshotSource? trustedSource,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(selection);
        if (!string.Equals(selection.GroupCode, "PAGED_VI", StringComparison.Ordinal) ||
            pageIndexZeroBased < 0)
        {
            throw new LegacySceneDataException("The VI paged selection is invalid.");
        }

        var page = checked(pageIndexZeroBased + 1);
        if (string.Equals(selection.Subject, SubjectSentinel, StringComparison.Ordinal))
        {
            if (trustedSource is null || !IsRowVersion(selection.DataCode))
            {
                throw new LegacySceneDataException(
                    "The trusted VI snapshot reference is unavailable or invalid.");
            }

            var snapshot = await trustedSource.ReadStockNameSnapshotAsync(cancellationToken)
                .ConfigureAwait(false);
            if (!IsRowVersion(snapshot.RowVersion) ||
                !string.Equals(
                    selection.DataCode,
                    snapshot.RowVersion,
                    StringComparison.Ordinal))
            {
                throw new LegacySceneDataException(
                    "The trusted VI snapshot changed after playlist creation.");
            }

            var names = ValidateTrustedNames(snapshot.StockNames);
            return new ViPagedQuoteLoadRequest(names, page);
        }

        // A version is meaningful only with the exact reserved sentinel. This closes
        // the downgrade path where a modified sentinel could otherwise be treated as
        // one historical stock name.
        if (!string.IsNullOrEmpty(selection.DataCode) ||
            selection.Subject.StartsWith("@MBN_VI_SNAPSHOT_", StringComparison.Ordinal))
        {
            throw new LegacySceneDataException("The trusted VI snapshot reference is invalid.");
        }

        return new ViPagedQuoteLoadRequest(ParseHistoricalNames(selection.Subject), page);
    }

    private static IReadOnlyList<string> ValidateTrustedNames(IReadOnlyList<string>? values)
    {
        if (values is null || values.Count is < 1 or > MaximumItemCount)
        {
            throw new LegacySceneDataException("The trusted VI snapshot is empty or invalid.");
        }

        var names = values.ToArray();
        if (names.Any(value =>
                string.IsNullOrWhiteSpace(value) ||
                value.Length > 200 ||
                !string.Equals(value, value.Trim(), StringComparison.Ordinal) ||
                value.Contains(',') ||
                value.Any(char.IsControl)))
        {
            throw new LegacySceneDataException("The trusted VI snapshot contains invalid data.");
        }

        return Array.AsReadOnly(names);
    }

    private static IReadOnlyList<string> ParseHistoricalNames(string value)
    {
        var names = value.Split(
            ',',
            StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
        if (names.Length is < 1 or > 240 || names.Any(name => name.Any(char.IsControl)))
        {
            throw new LegacySceneDataException("The VI stock-name selection is invalid.");
        }

        return Array.AsReadOnly(names);
    }
}
