using System.Text;

namespace GmRelay.Web.Services.Portfolio;

public static class PortfolioValidation
{
    private const int MinSlugLength = 3;
    private const int MaxSlugLength = 160;
    private const int MinTitleLength = 2;
    private const int MaxTitleLength = 255;
    private const int MaxDescriptionLength = 5000;
    private const int MinReviewBodyLength = 10;
    private const int MaxReviewBodyLength = 2000;

    private static readonly HashSet<string> AllowedFormats = new(StringComparer.Ordinal)
    {
        "Online",
        "Offline",
        "Hybrid"
    };

    public static string NormalizeSlug(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException("Slug must not be empty.");
        }

        var trimmed = value.Trim().ToLowerInvariant();

        var builder = new StringBuilder(trimmed.Length);
        var previousWasHyphen = false;
        foreach (var raw in trimmed)
        {
            char c;
            if (raw == ' ' || raw == '_' || raw == '-')
            {
                c = '-';
            }
            else if (IsAsciiAlphanumeric(raw))
            {
                c = raw;
            }
            else
            {
                throw new InvalidOperationException($"Slug contains unsupported character: '{raw}'.");
            }

            if (c == '-')
            {
                if (builder.Length == 0 || previousWasHyphen)
                {
                    continue;
                }

                builder.Append('-');
                previousWasHyphen = true;
            }
            else
            {
                builder.Append(c);
                previousWasHyphen = false;
            }
        }

        while (builder.Length > 0 && builder[^1] == '-')
        {
            builder.Length--;
        }

        if (builder.Length < MinSlugLength || builder.Length > MaxSlugLength)
        {
            throw new InvalidOperationException(
                $"Slug length must be between {MinSlugLength} and {MaxSlugLength} characters.");
        }

        // The normalization loop guarantees the output matches ^[a-z0-9]+(?:-[a-z0-9]+)*$,
        // so no post-loop regex check is required.
        return builder.ToString();
    }

    public static string NormalizeTitle(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException("Title must not be empty.");
        }

        var trimmed = value.Trim();
        if (trimmed.Length < MinTitleLength || trimmed.Length > MaxTitleLength)
        {
            throw new InvalidOperationException(
                $"Title length must be between {MinTitleLength} and {MaxTitleLength} characters.");
        }

        return trimmed;
    }

    public static string? NormalizeDescription(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var trimmed = value.Trim();
        if (trimmed.Length > MaxDescriptionLength)
        {
            throw new InvalidOperationException(
                $"Description must be at most {MaxDescriptionLength} characters.");
        }

        return trimmed;
    }

    public static string NormalizeReviewBody(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException("Review body must not be empty.");
        }

        var trimmed = value.Trim();
        if (trimmed.Length < MinReviewBodyLength || trimmed.Length > MaxReviewBodyLength)
        {
            throw new InvalidOperationException(
                $"Review body length must be between {MinReviewBodyLength} and {MaxReviewBodyLength} characters.");
        }

        return trimmed;
    }

    public static string? NormalizeFormat(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var trimmed = value.Trim();
        if (!AllowedFormats.Contains(trimmed))
        {
            throw new InvalidOperationException(
                $"Format must be one of: {string.Join(", ", AllowedFormats)}.");
        }

        return trimmed;
    }

    private static bool IsAsciiAlphanumeric(char c) =>
        (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
