package layoutroutingsecurityv1

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"io"
	"testing"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/internal/testvectors"
)

func TestConformanceVectors(t *testing.T) {
	var document conformanceDocument
	decoder := json.NewDecoder(bytes.NewReader(ConformanceVectorsJSON()))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&document); err != nil {
		t.Fatal(err)
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		t.Fatalf("unexpected trailing JSON: %v", err)
	}
	if document.SchemaVersion != "nexuscrm.layout-routing-security-view-conformance/v1" {
		t.Fatalf("schema = %q", document.SchemaVersion)
	}
	if len(document.Vectors) != 3 {
		t.Fatalf("vector count = %d, want 3", len(document.Vectors))
	}

	seen := map[string]bool{}
	for _, vector := range document.Vectors {
		t.Run(vector.Name, func(t *testing.T) {
			if vector.Name == "" || seen[vector.Name] {
				t.Fatalf("vector name %q is blank or duplicated", vector.Name)
			}
			seen[vector.Name] = true
			view := vector.View.view()
			canonical, err := CanonicalViewBytes(view)
			if err != nil {
				t.Fatal(err)
			}
			if got := hex.EncodeToString(canonical); got != vector.CanonicalBytesHex {
				t.Fatalf("canonical bytes = %s", got)
			}
			parsed, err := ParseCanonicalView(canonical)
			if err != nil {
				t.Fatalf("parse canonical bytes: %v", err)
			}
			parsedCanonical, err := CanonicalViewBytes(parsed)
			if err != nil {
				t.Fatalf("re-encode parsed view: %v", err)
			}
			if !bytes.Equal(parsedCanonical, canonical) {
				t.Fatal("parse/re-encode changed canonical bytes")
			}
			digest, err := ViewDigest(view)
			if err != nil {
				t.Fatal(err)
			}
			if digest != vector.ViewDigest {
				t.Fatalf("view digest = %s", digest)
			}
			parsedDigest, err := ViewDigest(parsed)
			if err != nil {
				t.Fatalf("digest parsed view: %v", err)
			}
			if parsedDigest != digest {
				t.Fatalf("parsed view digest = %s, want %s", parsedDigest, digest)
			}
		})
	}
	for _, required := range []string{
		"baseline-forest", "tombstones-multiple-roots-multibyte-org", "string-length-boundary",
	} {
		if !seen[required] {
			t.Fatalf("required vector %q missing", required)
		}
	}
}

func TestConformanceVectorsJSONReturnsDefensiveCopy(t *testing.T) {
	testvectors.AssertDefensiveCopy(t, ConformanceVectorsJSON)
}

type conformanceDocument struct {
	SchemaVersion string              `json:"schema_version"`
	Vectors       []conformanceVector `json:"vectors"`
}

type conformanceVector struct {
	Name              string          `json:"name"`
	View              conformanceView `json:"view"`
	CanonicalBytesHex string          `json:"canonical_bytes_hex"`
	ViewDigest        string          `json:"view_digest"`
}

type conformanceView struct {
	OrgID                      string               `json:"org_id"`
	CanonicalizationVersion    string               `json:"canonicalization_version"`
	KeyGrammarVersion          string               `json:"key_grammar_version"`
	RoleTopologyGrammarVersion string               `json:"role_topology_grammar_version"`
	Profiles                   []conformanceProfile `json:"profiles"`
	Roles                      []conformanceRole    `json:"roles"`
}

type conformanceProfile struct {
	ProfileKey      conformanceKey `json:"profile_key"`
	ResolvedProfile string         `json:"resolved_profile_id"`
	State           IdentityState  `json:"state"`
}

type conformanceRole struct {
	RoleKey      conformanceKey  `json:"role_key"`
	ResolvedRole string          `json:"resolved_role_id"`
	State        IdentityState   `json:"state"`
	Parent       *conformanceKey `json:"parent_role_key"`
}

type conformanceKey struct {
	NamespaceID string `json:"namespace_id"`
	LocalKey    string `json:"local_key"`
}

func (value conformanceView) view() View {
	profiles := make([]ProfileIdentity, len(value.Profiles))
	for index, profile := range value.Profiles {
		profiles[index] = ProfileIdentity{
			Key:               ProfileKey{NamespaceID: profile.ProfileKey.NamespaceID, LocalKey: profile.ProfileKey.LocalKey},
			ResolvedProfileID: profile.ResolvedProfile, State: profile.State,
		}
	}
	roles := make([]RoleIdentity, len(value.Roles))
	for index, role := range value.Roles {
		var parent *RoleKey
		if role.Parent != nil {
			parent = &RoleKey{NamespaceID: role.Parent.NamespaceID, LocalKey: role.Parent.LocalKey}
		}
		roles[index] = RoleIdentity{
			Key:            RoleKey{NamespaceID: role.RoleKey.NamespaceID, LocalKey: role.RoleKey.LocalKey},
			ResolvedRoleID: role.ResolvedRole, State: role.State, ParentRoleKey: parent,
		}
	}
	return View{
		OrgID: value.OrgID, CanonicalizationVersion: value.CanonicalizationVersion,
		KeyGrammarVersion:          value.KeyGrammarVersion,
		RoleTopologyGrammarVersion: value.RoleTopologyGrammarVersion,
		Profiles:                   profiles, Roles: roles,
	}
}
