// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package ipproto

import (
	"encoding"
	"encoding/json"
	"fmt"
	"testing"

	"tailscale.com/util/must"
)

// Ensure that the Proto type implements encoding.TextMarshaler and
// encoding.TextUnmarshaler.
var (
	_ encoding.TextMarshaler   = (*Proto)(nil)
	_ encoding.TextUnmarshaler = (*Proto)(nil)
)

func TestHistoricalStringNames(t *testing.T) {
	// A subset of supported protocols were described with their lowercase String() representations and must remain supported.
	var historical = map[string]Proto{
		"icmpv4": ICMPv4,
		"igmp":   IGMP,
		"tcp":    TCP,
		"udp":    UDP,
		"dccp":   DCCP,
		"gre":    GRE,
		"sctp":   SCTP,
	}

	for name, proto := range historical {
		var p Proto
		must.Do(p.UnmarshalText([]byte(name)))
		if got, want := p, proto; got != want {
			t.Errorf("Proto.UnmarshalText(%q) = %v, want %v", name, got, want)
		}
	}
}

func TestAcceptedNamesContainsPreferredNames(t *testing.T) {
	for proto, name := range preferredNames {
		if _, ok := acceptedNames[name]; !ok {
			t.Errorf("preferredNames[%q] = %v, but acceptedNames does not contain it", name, proto)
		}
	}
}

func TestProtoTextEncodingRoundTrip(t *testing.T) {
	for i := range 256 {
		text := must.Get(Proto(i).MarshalText())
		var p Proto
		must.Do(p.UnmarshalText(text))

		if got, want := p, Proto(i); got != want {
			t.Errorf("Proto(%d) round-trip got %v, want %v", i, got, want)
		}
	}
}

func TestProtoUnmarshalText(t *testing.T) {
	var p Proto = 1
	err := p.UnmarshalText([]byte(nil))
	if err != nil || p != 0 {
		t.Fatalf("empty input, got err=%v, p=%v, want nil, 0", err, p)
	}

	for i := range 256 {
		var p Proto
		must.Do(p.UnmarshalText(fmt.Appendf(nil, "%d", i)))
		if got, want := p, Proto(i); got != want {
			t.Errorf("Proto(%d) = %v, want %v", i, got, want)
		}
	}

	for name, wantProto := range acceptedNames {
		var p Proto
		must.Do(p.UnmarshalText([]byte(name)))
		if got, want := p, wantProto; got != want {
			t.Errorf("Proto(%q) = %v, want %v", name, got, want)
		}
	}

	for wantProto, name := range preferredNames {
		var p Proto
		must.Do(p.UnmarshalText([]byte(name)))
		if got, want := p, wantProto; got != want {
			t.Errorf("Proto(%q) = %v, want %v", name, got, want)
		}
	}
}

func TestProtoMarshalText(t *testing.T) {
	for i := range 256 {
		text := must.Get(Proto(i).MarshalText())

		if wantName, ok := preferredNames[Proto(i)]; ok {
			if got, want := string(text), wantName; got != want {
				t.Errorf("Proto(%d).MarshalText() = %q, want preferred name %q", i, got, want)
			}
			continue
		}

		if got, want := string(text), fmt.Sprintf("%d", i); got != want {
			t.Errorf("Proto(%d).MarshalText() = %q, want %q", i, got, want)
		}
	}
}

func TestProtoMarshalJSON(t *testing.T) {
	for i := range 256 {
		j := must.Get(Proto(i).MarshalJSON())
		if got, want := string(j), fmt.Sprintf(`%d`, i); got != want {
			t.Errorf("Proto(%d).MarshalJSON() = %q, want %q", i, got, want)
		}
	}
}

func TestProtoUnmarshalJSON(t *testing.T) {
	var p Proto

	for i := range 256 {
		j := fmt.Appendf(nil, `%d`, i)
		must.Do(json.Unmarshal(j, &p))
		if got, want := p, Proto(i); got != want {
			t.Errorf("Proto(%d) = %v, want %v", i, got, want)
		}
	}

	for name, wantProto := range acceptedNames {
		must.Do(json.Unmarshal(fmt.Appendf(nil, `"%s"`, name), &p))
		if got, want := p, wantProto; got != want {
			t.Errorf("Proto(%q) = %v, want %v", name, got, want)
		}
	}
}
