using System.Linq;
using System.Text;
using NuGet.Versioning;
using StellaOps.Feedser.Normalization.Identifiers;

namespace StellaOps.Feedser.Source.Common.Packages;

/// <summary>
/// Shared helpers for working with Package URLs and SemVer coordinates inside connectors.
/// </summary>
public static class PackageCoordinateHelper
{
    public static bool TryParsePackageUrl(string? value, out PackageCoordinates? coordinates)
    {
        coordinates = null;
        if (!IdentifierNormalizer.TryNormalizePackageUrl(value, out var canonical, out var packageUrl) || packageUrl is null)
        {
            return false;
        }

        var namespaceSegments = packageUrl.NamespaceSegments.ToArray();
        var subpathSegments = packageUrl.SubpathSegments.ToArray();
        var qualifiers = packageUrl.Qualifiers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
        var canonicalRebuilt = BuildPackageUrl(
            packageUrl.Type,
            namespaceSegments,
            packageUrl.Name,
            packageUrl.Version,
            qualifiers,
            subpathSegments);

        coordinates = new PackageCoordinates(
            Canonical: canonicalRebuilt,
            Type: packageUrl.Type,
            NamespaceSegments: namespaceSegments,
            Name: packageUrl.Name,
            Version: packageUrl.Version,
            Qualifiers: qualifiers,
            SubpathSegments: subpathSegments,
            Original: packageUrl.Original);
        return true;
    }

    public static PackageCoordinates ParsePackageUrl(string value)
    {
        if (!TryParsePackageUrl(value, out var coordinates) || coordinates is null)
        {
            throw new FormatException($"Value '{value}' is not a valid Package URL");
        }

        return coordinates;
    }

    public static bool TryParseSemVer(string? value, out SemanticVersion? version, out string? normalized)
    {
        version = null;
        normalized = null;

        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        if (!SemanticVersion.TryParse(value.Trim(), out var parsed))
        {
            return false;
        }

        version = parsed;
        normalized = parsed.ToNormalizedString();
        return true;
    }

    public static bool TryParseSemVerRange(string? value, out VersionRange? range)
    {
        range = null;
        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        var trimmed = value.Trim();
        if (trimmed.StartsWith("^", StringComparison.Ordinal))
        {
            var baseSegment = trimmed[1..];
            if (!SemanticVersion.TryParse(baseSegment, out var baseVersion))
            {
                return false;
            }

            var upperBound = CalculateCaretUpperBound(baseVersion);
            var caretExpression = $"[{baseVersion.ToNormalizedString()}, {upperBound.ToNormalizedString()})";
            if (VersionRange.TryParse(caretExpression, out var caretRange))
            {
                range = caretRange;
                return true;
            }

            return false;
        }

        if (!VersionRange.TryParse(trimmed, out var parsed))
        {
            try
            {
                parsed = VersionRange.Parse(trimmed);
            }
            catch
            {
                return false;
            }
        }

        range = parsed;
        return true;
    }

    public static string BuildPackageUrl(
        string type,
        IReadOnlyList<string>? namespaceSegments,
        string name,
        string? version = null,
        IReadOnlyDictionary<string, string>? qualifiers = null,
        IReadOnlyList<string>? subpathSegments = null)
    {
        ArgumentException.ThrowIfNullOrEmpty(type);
        ArgumentException.ThrowIfNullOrEmpty(name);

        var builder = new StringBuilder("pkg:");
        builder.Append(type.Trim().ToLowerInvariant());
        builder.Append('/');

        if (namespaceSegments is not null && namespaceSegments.Count > 0)
        {
            builder.Append(string.Join('/', namespaceSegments.Select(NormalizeSegment)));
            builder.Append('/');
        }

        builder.Append(NormalizeSegment(name));

        if (!string.IsNullOrWhiteSpace(version))
        {
            builder.Append('@');
            builder.Append(version.Trim());
        }

        if (qualifiers is not null && qualifiers.Count > 0)
        {
            builder.Append('?');
            builder.Append(string.Join('&', qualifiers
                .OrderBy(static kvp => kvp.Key, StringComparer.OrdinalIgnoreCase)
                .Select(kvp => $"{NormalizeSegment(kvp.Key)}={NormalizeSegment(kvp.Value)}")));
        }

        if (subpathSegments is not null && subpathSegments.Count > 0)
        {
            builder.Append('#');
            builder.Append(string.Join('/', subpathSegments.Select(NormalizeSegment)));
        }

        return builder.ToString();
    }

    private static string NormalizeSegment(string value)
    {
        ArgumentNullException.ThrowIfNull(value);
        var trimmed = value.Trim();
        var unescaped = Uri.UnescapeDataString(trimmed);
        var encoded = Uri.EscapeDataString(unescaped);
        return encoded.Replace("%40", "@");
    }

    private static SemanticVersion CalculateCaretUpperBound(SemanticVersion baseVersion)
    {
        if (baseVersion.Major > 0)
        {
            return new SemanticVersion(baseVersion.Major + 1, 0, 0);
        }

        if (baseVersion.Minor > 0)
        {
            return new SemanticVersion(0, baseVersion.Minor + 1, 0);
        }

        return new SemanticVersion(0, 0, baseVersion.Patch + 1);
    }
}

public sealed record PackageCoordinates(
    string Canonical,
    string Type,
    IReadOnlyList<string> NamespaceSegments,
    string Name,
    string? Version,
    IReadOnlyDictionary<string, string> Qualifiers,
    IReadOnlyList<string> SubpathSegments,
    string Original);
