using System.Globalization;
using System.Text;

namespace Nexus.Api.Repositories;

internal readonly record struct OpenClawRunCursorPosition(
    DateTimeOffset CreatedAt,
    Guid? Id);

internal static class OpenClawRunCursorCodec
{
    private const string Version = "v1";
    private const int MaximumEncodedLength = 128;

    public static string Encode(DateTimeOffset createdAt, Guid id)
    {
        var payload = string.Create(
            CultureInfo.InvariantCulture,
            $"{Version}|{createdAt.UtcTicks}|{id:N}");

        return Convert.ToBase64String(Encoding.UTF8.GetBytes(payload))
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }

    public static bool TryDecode(
        string? cursor,
        out OpenClawRunCursorPosition position)
    {
        position = default;
        if (string.IsNullOrWhiteSpace(cursor) || cursor.Length > MaximumEncodedLength)
            return false;

        // Compatibility with the original run cursor, which was an unversioned
        // UTC tick value and therefore cannot express the Id tie-breaker.
        if (long.TryParse(
                cursor,
                NumberStyles.None,
                CultureInfo.InvariantCulture,
                out var legacyTicks))
        {
            return TryCreatePosition(legacyTicks, null, out position);
        }

        try
        {
            var normalized = cursor
                .Replace('-', '+')
                .Replace('_', '/');

            normalized = (normalized.Length % 4) switch
            {
                0 => normalized,
                2 => normalized + "==",
                3 => normalized + "=",
                _ => throw new FormatException("Invalid Base64Url length.")
            };

            var payload = new UTF8Encoding(
                    encoderShouldEmitUTF8Identifier: false,
                    throwOnInvalidBytes: true)
                .GetString(Convert.FromBase64String(normalized));
            var parts = payload.Split('|');
            if (parts.Length != 3
                || !string.Equals(parts[0], Version, StringComparison.Ordinal)
                || !long.TryParse(
                    parts[1],
                    NumberStyles.None,
                    CultureInfo.InvariantCulture,
                    out var utcTicks)
                || !Guid.TryParseExact(parts[2], "N", out var id))
            {
                return false;
            }

            return TryCreatePosition(utcTicks, id, out position);
        }
        catch (Exception exception) when (
            exception is FormatException
            or DecoderFallbackException)
        {
            return false;
        }
    }

    private static bool TryCreatePosition(
        long utcTicks,
        Guid? id,
        out OpenClawRunCursorPosition position)
    {
        position = default;
        try
        {
            position = new OpenClawRunCursorPosition(
                new DateTimeOffset(utcTicks, TimeSpan.Zero),
                id);
            return true;
        }
        catch (ArgumentOutOfRangeException)
        {
            return false;
        }
    }
}
