package layoutroutingidentitymappingv1

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

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

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-identity-mapping-proposal-conformance/v1" {
		t.Fatalf("schema = %q", document.SchemaVersion)
	}
	if len(document.Vectors) != 2 {
		t.Fatalf("vector count = %d, want 2", 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
			proposal := vector.Proposal.proposal()
			raw, err := CanonicalProposalBytes(proposal)
			if err != nil {
				t.Fatal(err)
			}
			if got := hex.EncodeToString(raw); got != vector.CanonicalBytesHex {
				t.Fatalf("canonical bytes = %s", got)
			}
			parsed, err := ParseCanonicalProposal(raw)
			if err != nil {
				t.Fatalf("parse canonical bytes: %v", err)
			}
			parsedRaw, err := CanonicalProposalBytes(parsed)
			if err != nil {
				t.Fatalf("re-encode parsed proposal: %v", err)
			}
			if !bytes.Equal(raw, parsedRaw) {
				t.Fatal("parse/re-encode changed canonical bytes")
			}
			digest, err := ProposalDigest(proposal)
			if err != nil {
				t.Fatal(err)
			}
			if digest != vector.ProposalDigest {
				t.Fatalf("proposal digest = %s", digest)
			}
		})
	}
	for _, required := range []string{"baseline", "multibyte-org"} {
		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"`
	Proposal          conformanceProposal `json:"proposal"`
	CanonicalBytesHex string              `json:"canonical_bytes_hex"`
	ProposalDigest    string              `json:"proposal_digest"`
}

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

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

type conformanceRole struct {
	RoleKey      conformanceKey `json:"role_key"`
	ResolvedRole string         `json:"resolved_role_id"`
}

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

func (value conformanceProposal) proposal() Proposal {
	profiles := make([]ProfileMapping, len(value.Profiles))
	for index, profile := range value.Profiles {
		profiles[index] = ProfileMapping{
			Key:               routing.ProfileKey{NamespaceID: profile.ProfileKey.NamespaceID, LocalKey: profile.ProfileKey.LocalKey},
			ResolvedProfileID: profile.ResolvedProfile,
		}
	}
	roles := make([]RoleMapping, len(value.Roles))
	for index, role := range value.Roles {
		roles[index] = RoleMapping{
			Key:            routing.RoleKey{NamespaceID: role.RoleKey.NamespaceID, LocalKey: role.RoleKey.LocalKey},
			ResolvedRoleID: role.ResolvedRole,
		}
	}
	return Proposal{
		OrgID:                   value.OrgID,
		CanonicalizationVersion: value.CanonicalizationVersion,
		KeyGrammarVersion:       value.KeyGrammarVersion,
		Profiles:                profiles,
		Roles:                   roles,
	}
}
