using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace StellaOps.Feedser.Source.Vndr.Apple;

public sealed class AppleOptions : IValidatableObject
{
    public const string HttpClientName = "feedser-vndr-apple";

    /// <summary>
    /// Gets or sets the JSON endpoint that lists software metadata (defaults to Apple Software Lookup Service).
    /// </summary>
    public Uri? SoftwareLookupUri { get; set; } = new("https://gdmf.apple.com/v2/pmv");

    /// <summary>
    /// Gets or sets the base URI for HT advisory pages (locale neutral); trailing slash required.
    /// </summary>
    public Uri? AdvisoryBaseUri { get; set; } = new("https://support.apple.com/");

    /// <summary>
    /// Gets or sets the locale segment inserted between the base URI and HT identifier, e.g. "en-us".
    /// </summary>
    public string LocaleSegment { get; set; } = "en-us";

    /// <summary>
    /// Maximum advisories to fetch per run; defaults to 50.
    /// </summary>
    public int MaxAdvisoriesPerFetch { get; set; } = 50;

    /// <summary>
    /// Sliding backfill window for initial sync (defaults to 90 days).
    /// </summary>
    public TimeSpan InitialBackfill { get; set; } = TimeSpan.FromDays(90);

    /// <summary>
    /// Tolerance added to the modified timestamp comparisons during resume.
    /// </summary>
    public TimeSpan ModifiedTolerance { get; set; } = TimeSpan.FromHours(1);

    /// <summary>
    /// Optional allowlist of HT identifiers to include; empty means include all.
    /// </summary>
    public HashSet<string> AdvisoryAllowlist { get; } = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Optional blocklist of HT identifiers to skip (e.g. non-security bulletins that share the feed).
    /// </summary>
    public HashSet<string> AdvisoryBlocklist { get; } = new(StringComparer.OrdinalIgnoreCase);

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (SoftwareLookupUri is null)
        {
            yield return new ValidationResult("SoftwareLookupUri must be provided.", new[] { nameof(SoftwareLookupUri) });
        }
        else if (!SoftwareLookupUri.IsAbsoluteUri)
        {
            yield return new ValidationResult("SoftwareLookupUri must be absolute.", new[] { nameof(SoftwareLookupUri) });
        }

        if (AdvisoryBaseUri is null)
        {
            yield return new ValidationResult("AdvisoryBaseUri must be provided.", new[] { nameof(AdvisoryBaseUri) });
        }
        else if (!AdvisoryBaseUri.IsAbsoluteUri)
        {
            yield return new ValidationResult("AdvisoryBaseUri must be absolute.", new[] { nameof(AdvisoryBaseUri) });
        }

        if (string.IsNullOrWhiteSpace(LocaleSegment))
        {
            yield return new ValidationResult("LocaleSegment must be specified.", new[] { nameof(LocaleSegment) });
        }

        if (MaxAdvisoriesPerFetch <= 0)
        {
            yield return new ValidationResult("MaxAdvisoriesPerFetch must be greater than zero.", new[] { nameof(MaxAdvisoriesPerFetch) });
        }

        if (InitialBackfill <= TimeSpan.Zero)
        {
            yield return new ValidationResult("InitialBackfill must be positive.", new[] { nameof(InitialBackfill) });
        }

        if (ModifiedTolerance < TimeSpan.Zero)
        {
            yield return new ValidationResult("ModifiedTolerance cannot be negative.", new[] { nameof(ModifiedTolerance) });
        }
    }

    public void Validate()
    {
        var context = new ValidationContext(this);
        var results = new List<ValidationResult>();
        if (!Validator.TryValidateObject(this, context, results, validateAllProperties: true))
        {
            throw new ValidationException(string.Join("; ", results));
        }
    }
}
