using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace FrameProcessor.Domain;

/// <summary>
/// A 48-bit MAC address. Canonical form is lowercase hex without separators (e.g. <c>aabbccddeeff</c>);
/// parsing accepts colon, hyphen, or unseparated hex with arbitrary surrounding whitespace.
/// </summary>
[JsonConverter(typeof(MacAddressJsonConverter))]
public readonly record struct MacAddress : IParsable<MacAddress>, ISpanParsable<MacAddress>
{
    private readonly byte _b0;
    private readonly byte _b1;
    private readonly byte _b2;
    private readonly byte _b3;
    private readonly byte _b4;
    private readonly byte _b5;

    public MacAddress(ReadOnlySpan<byte> bytes)
    {
        if (bytes.Length != 6)
        {
            throw new ArgumentException("MAC address must be exactly 6 bytes.", nameof(bytes));
        }

        _b0 = bytes[0];
        _b1 = bytes[1];
        _b2 = bytes[2];
        _b3 = bytes[3];
        _b4 = bytes[4];
        _b5 = bytes[5];
    }

    public override string ToString() => string.Create(12, this, static (span, mac) =>
    {
        WriteHex(span[..2], mac._b0);
        WriteHex(span.Slice(2, 2), mac._b1);
        WriteHex(span.Slice(4, 2), mac._b2);
        WriteHex(span.Slice(6, 2), mac._b3);
        WriteHex(span.Slice(8, 2), mac._b4);
        WriteHex(span.Slice(10, 2), mac._b5);
    });

    public static bool TryParse(string? s, out MacAddress result)
    {
        if (s is null)
        {
            result = default;
            return false;
        }

        return TryParse(s.AsSpan(), out result);
    }

    public static bool TryParse(ReadOnlySpan<char> s, out MacAddress result)
    {
        result = default;
        s = s.Trim();
        if (s.IsEmpty)
        {
            return false;
        }

        Span<char> hex = stackalloc char[12];
        var written = 0;
        foreach (var c in s)
        {
            if (c is ':' or '-')
            {
                continue;
            }

            if (written == 12)
            {
                return false;
            }

            if (!IsHexDigit(c))
            {
                return false;
            }

            hex[written++] = c;
        }

        if (written != 12)
        {
            return false;
        }

        Span<byte> bytes = stackalloc byte[6];
        for (var i = 0; i < 6; i++)
        {
            if (!byte.TryParse(hex.Slice(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var b))
            {
                return false;
            }

            bytes[i] = b;
        }

        result = new MacAddress(bytes);
        return true;
    }

    public static MacAddress Parse(string s)
    {
        ArgumentNullException.ThrowIfNull(s);
        return TryParse(s, out var result)
            ? result
            : throw new FormatException($"'{s}' is not a valid MAC address.");
    }

    public static MacAddress Parse(string s, IFormatProvider? provider) => Parse(s);

    public static bool TryParse(string? s, IFormatProvider? provider, out MacAddress result)
        => TryParse(s, out result);

    public static MacAddress Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
        => TryParse(s, out var result)
            ? result
            : throw new FormatException($"'{s}' is not a valid MAC address.");

    public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out MacAddress result)
        => TryParse(s, out result);

    private static bool IsHexDigit(char c)
        => (uint)(c - '0') <= 9 || (uint)(c - 'a') <= 5 || (uint)(c - 'A') <= 5;

    private static void WriteHex(Span<char> dest, byte value)
    {
        dest[0] = HexChar(value >> 4);
        dest[1] = HexChar(value & 0xF);
    }

    private static char HexChar(int nibble) => (char)(nibble < 10 ? '0' + nibble : 'a' + (nibble - 10));
}

internal sealed class MacAddressJsonConverter : JsonConverter<MacAddress>
{
    public override MacAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var s = reader.GetString();
        if (!MacAddress.TryParse(s, out var mac))
        {
            throw new JsonException($"'{s}' is not a valid MAC address.");
        }

        return mac;
    }

    public override void Write(Utf8JsonWriter writer, MacAddress value, JsonSerializerOptions options)
        => writer.WriteStringValue(value.ToString());
}
