using System;
using System.Collections.Generic;

namespace StellaOps.Feedser.Source.Cccs.Configuration;

public sealed class CccsOptions
{
    public const string HttpClientName = "feedser.source.cccs";

    private readonly List<CccsFeedEndpoint> _feeds = new();

    public CccsOptions()
    {
        _feeds.Add(new CccsFeedEndpoint("en", new Uri("https://www.cyber.gc.ca/api/cccs/threats/v1/get?lang=en&content_type=cccs_threat")));
        _feeds.Add(new CccsFeedEndpoint("fr", new Uri("https://www.cyber.gc.ca/api/cccs/threats/v1/get?lang=fr&content_type=cccs_threat")));
    }

    /// <summary>
    /// Feed endpoints to poll; configure per language or content category.
    /// </summary>
    public IList<CccsFeedEndpoint> Feeds => _feeds;

    /// <summary>
    /// Maximum number of entries to enqueue per fetch cycle.
    /// </summary>
    public int MaxEntriesPerFetch { get; set; } = 80;

    /// <summary>
    /// Maximum remembered entries (URI+hash) for deduplication.
    /// </summary>
    public int MaxKnownEntries { get; set; } = 512;

    /// <summary>
    /// Timeout applied to feed and taxonomy requests.
    /// </summary>
    public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Delay between successive feed requests to respect upstream throttling.
    /// </summary>
    public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);

    /// <summary>
    /// Backoff recorded in source state when fetch fails.
    /// </summary>
    public TimeSpan FailureBackoff { get; set; } = TimeSpan.FromMinutes(1);

    public void Validate()
    {
        if (_feeds.Count == 0)
        {
            throw new InvalidOperationException("At least one CCCS feed endpoint must be configured.");
        }

        var seenLanguages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var feed in _feeds)
        {
            feed.Validate();
            if (!seenLanguages.Add(feed.Language))
            {
                throw new InvalidOperationException($"Duplicate CCCS feed language configured: '{feed.Language}'. Each language should be unique to avoid duplicate ingestion.");
            }
        }

        if (MaxEntriesPerFetch <= 0)
        {
            throw new InvalidOperationException($"{nameof(MaxEntriesPerFetch)} must be greater than zero.");
        }

        if (MaxKnownEntries <= 0)
        {
            throw new InvalidOperationException($"{nameof(MaxKnownEntries)} must be greater than zero.");
        }

        if (RequestTimeout <= TimeSpan.Zero)
        {
            throw new InvalidOperationException($"{nameof(RequestTimeout)} must be positive.");
        }

        if (RequestDelay < TimeSpan.Zero)
        {
            throw new InvalidOperationException($"{nameof(RequestDelay)} cannot be negative.");
        }

        if (FailureBackoff <= TimeSpan.Zero)
        {
            throw new InvalidOperationException($"{nameof(FailureBackoff)} must be positive.");
        }
    }
}

public sealed class CccsFeedEndpoint
{
    public CccsFeedEndpoint()
    {
    }

    public CccsFeedEndpoint(string language, Uri uri)
    {
        Language = language;
        Uri = uri;
    }

    public string Language { get; set; } = "en";

    public Uri? Uri { get; set; }

    public void Validate()
    {
        if (string.IsNullOrWhiteSpace(Language))
        {
            throw new InvalidOperationException("CCCS feed language must be specified.");
        }

        if (Uri is null || !Uri.IsAbsoluteUri)
        {
            throw new InvalidOperationException($"CCCS feed endpoint URI must be an absolute URI (language='{Language}').");
        }
    }

    public Uri BuildTaxonomyUri()
    {
        if (Uri is null)
        {
            throw new InvalidOperationException("Feed endpoint URI must be configured before building taxonomy URI.");
        }

        var language = Uri.GetQueryParameterValueOrDefault("lang", Language);
        var builder = $"https://www.cyber.gc.ca/api/cccs/taxonomy/v1/get?lang={language}&vocabulary=cccs_alert_type";
        return new Uri(builder, UriKind.Absolute);
    }
}

internal static class CccsUriExtensions
{
    public static string GetQueryParameterValueOrDefault(this Uri uri, string key, string fallback)
    {
        if (uri is null)
        {
            return fallback;
        }

        var query = uri.Query;
        if (string.IsNullOrEmpty(query))
        {
            return fallback;
        }

        var trimmed = query.StartsWith("?", StringComparison.Ordinal) ? query[1..] : query;
        foreach (var pair in trimmed.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
        {
            var separatorIndex = pair.IndexOf('=');
            if (separatorIndex < 0)
            {
                continue;
            }

            var left = pair[..separatorIndex].Trim();
            if (!left.Equals(key, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var right = pair[(separatorIndex + 1)..].Trim();
            if (right.Length == 0)
            {
                continue;
            }

            return Uri.UnescapeDataString(right);
        }

        return fallback;
    }
}
