package systemlayoutbundleauthorityv1

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"strconv"
	"strings"
	"testing"

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

func TestImmutableConformanceVectors(t *testing.T) {
	document := testvectors.DecodeVectorDocument[vectorDocument](t, ConformanceVectorsJSON())
	if document.SchemaVersion != "nexuscrm.layout-bundle-system-authority.conformance/v1" {
		t.Fatalf("schema_version = %q", document.SchemaVersion)
	}
	if document.SlotValueVectors == nil || document.CommandIDVectors == nil || document.CommandRequestVectors == nil ||
		document.EvidenceVectors == nil || document.ULEB128Vectors == nil || document.RejectionVectors == nil {
		t.Fatal("every vector collection must be a present JSON array")
	}
	if len(document.SlotValueVectors) != 2 || len(document.CommandIDVectors) != 5 ||
		len(document.CommandRequestVectors) != 2 || len(document.EvidenceVectors) != 3 ||
		len(document.ULEB128Vectors) != 7 || len(document.RejectionVectors) != 10 {
		t.Fatalf(
			"vector counts = %d/%d/%d/%d/%d/%d",
			len(document.SlotValueVectors), len(document.CommandIDVectors), len(document.CommandRequestVectors),
			len(document.EvidenceVectors), len(document.ULEB128Vectors), len(document.RejectionVectors),
		)
	}

	seenNames := map[string]bool{}
	slots := make(map[string]SystemLayoutBundleSlotV1, len(document.SlotValueVectors))
	for _, vector := range document.SlotValueVectors {
		t.Run("slot/"+vector.Name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, vector.Name)
			value := vector.SlotValue.value()
			canonical, err := CanonicalSlotValueBytes(value)
			if err != nil {
				t.Fatal(err)
			}
			assertCanonicalHex(t, canonical, vector.CanonicalBytesHex)
			digestValue, err := SlotValueDigest(value)
			if err != nil || digestValue != vector.SlotValueDigest {
				t.Fatalf("slot digest = %q, %v", digestValue, err)
			}
			slots[vector.Name] = value
		})
	}
	for _, required := range []string{"active", "tombstone"} {
		if _, exists := slots[required]; !exists {
			t.Fatalf("required slot vector %q is absent", required)
		}
	}

	for _, vector := range document.CommandIDVectors {
		t.Run("command-id/"+vector.Name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, vector.Name)
			err := ValidateCommandID(CommandID(vector.CommandID))
			if vector.Valid && err != nil {
				t.Fatalf("valid CommandID rejected: %v", err)
			}
			if !vector.Valid && err == nil {
				t.Fatal("invalid CommandID accepted")
			}
		})
	}

	requests := make(map[string]SystemLayoutBundleSlotCommandRequest, len(document.CommandRequestVectors))
	for _, vector := range document.CommandRequestVectors {
		t.Run("request/"+vector.Name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, vector.Name)
			if err := ValidateCommandID(CommandID(vector.CommandID)); err != nil {
				t.Fatalf("vector CommandID: %v", err)
			}
			request := vector.Request.value()
			canonical, err := CanonicalCommandRequestBytes(request)
			if err != nil {
				t.Fatal(err)
			}
			assertCanonicalHex(t, canonical, vector.CanonicalBytesHex)
			digestValue, err := CommandRequestDigest(request)
			if err != nil || digestValue != vector.RequestDigest {
				t.Fatalf("request digest = %q, %v", digestValue, err)
			}
			requests[vector.Name] = request
		})
	}
	for _, required := range []string{"create-absent", "retire-exact"} {
		if _, exists := requests[required]; !exists {
			t.Fatalf("required request vector %q is absent", required)
		}
	}

	evidenceValues := make(map[string]SystemLayoutBundleAuthorityEvidenceV1, len(document.EvidenceVectors))
	for _, vector := range document.EvidenceVectors {
		t.Run("evidence/"+vector.Name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, vector.Name)
			evidence := vector.Evidence.value(t)
			canonical, err := CanonicalEvidenceBytes(evidence)
			if err != nil {
				t.Fatal(err)
			}
			assertCanonicalHex(t, canonical, vector.CanonicalBytesHex)
			digestValue, err := EvidenceDigest(evidence)
			if err != nil || digestValue != vector.EvidenceDigest {
				t.Fatalf("evidence digest = %q, %v", digestValue, err)
			}
			evidenceValues[vector.Name] = evidence
		})
	}
	for _, required := range []string{"baseline", "utf8-source", "multibyte-uleb128-source"} {
		if _, exists := evidenceValues[required]; !exists {
			t.Fatalf("required evidence vector %q is absent", required)
		}
	}

	for _, vector := range document.ULEB128Vectors {
		name := "uleb128-" + vector.ValueDecimal
		t.Run(name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, name)
			value := parseCanonicalUint(t, "value_decimal", vector.ValueDecimal)
			var encoded [binary.MaxVarintLen64]byte
			count := binary.PutUvarint(encoded[:], value)
			if got := hex.EncodeToString(encoded[:count]); got != vector.EncodedHex {
				t.Fatalf("encoded bytes = %s, want %s", got, vector.EncodedHex)
			}
		})
	}

	for _, vector := range document.RejectionVectors {
		t.Run("rejection/"+vector.Name, func(t *testing.T) {
			testvectors.AssertUniqueVectorName(t, seenNames, vector.Name)
			err := applyRejectionVector(t, vector, slots, requests, evidenceValues)
			if err == nil || !strings.Contains(err.Error(), vector.ErrorContains) {
				t.Fatalf("rejection error = %v, want substring %q", err, vector.ErrorContains)
			}
		})
	}

}

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

func applyRejectionVector(
	t *testing.T,
	vector rejectionVector,
	slots map[string]SystemLayoutBundleSlotV1,
	requests map[string]SystemLayoutBundleSlotCommandRequest,
	evidenceValues map[string]SystemLayoutBundleAuthorityEvidenceV1,
) error {
	t.Helper()
	switch vector.Target {
	case "slot":
		value, exists := slots[vector.BaseVector]
		if !exists {
			t.Fatalf("unknown slot base vector %q", vector.BaseVector)
		}
		switch vector.Mutation {
		case "BundleKey.SlotOwnerKind=tenant":
			value.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKindTenant
		case "BundleKey.SlotOwnerNamespace=io.nexuscrm.other":
			value.BundleKey.SlotOwnerNamespace = "io.nexuscrm.other"
		case "BundleKey.LocalComponentKey=uppercase":
			value.BundleKey.LocalComponentKey = layoutbundlekeyv1.LocalComponentKey("key256:" + strings.Repeat("A", 64))
		default:
			t.Fatalf("unsupported slot mutation %q", vector.Mutation)
		}
		return ValidateSlotValue(value)
	case "request":
		value, exists := requests[vector.BaseVector]
		if !exists {
			t.Fatalf("unknown request base vector %q", vector.BaseVector)
		}
		switch vector.Mutation {
		case "TargetBundleKey.LocalComponentKey=key256:2222...":
			value.TargetBundleKey.LocalComponentKey = key("2")
		case "DesiredSlotValue.State=TOMBSTONE":
			value.DesiredSlotValue.State = SlotStateTombstone
		case "ExpectedPrior=EXACT(active digest)":
			digestValue, err := SlotValueDigest(value.DesiredSlotValue)
			if err != nil {
				t.Fatal(err)
			}
			value.ExpectedPrior = ExpectedPrior{Variant: ExpectedPriorExact, SlotValueDigest: digestValue}
		case "ExpectedPrior=ABSENT":
			value.ExpectedPrior = ExpectedPrior{Variant: ExpectedPriorAbsent}
		case "ExpectedPrior.SlotValueDigest=sha256:9999...":
			value.ExpectedPrior.SlotValueDigest = digest("9")
		default:
			t.Fatalf("unsupported request mutation %q", vector.Mutation)
		}
		return ValidateCommandRequest(value)
	case "evidence":
		value, exists := evidenceValues[vector.BaseVector]
		if !exists {
			t.Fatalf("unknown evidence base vector %q", vector.BaseVector)
		}
		switch vector.Mutation {
		case "SlotState=TOMBSTONE":
			value.SlotState = SlotStateTombstone
		case "SourceCatalogSnapshot.CatalogRevision=0":
			value.SourceCatalogSnapshot.CatalogRevision = 0
		default:
			t.Fatalf("unsupported evidence mutation %q", vector.Mutation)
		}
		return ValidateEvidence(value)
	default:
		t.Fatalf("unsupported rejection target %q", vector.Target)
		return nil
	}
}

func assertCanonicalHex(t *testing.T, canonical []byte, want string) {
	t.Helper()
	wantBytes, err := hex.DecodeString(want)
	if err != nil {
		t.Fatalf("golden canonical hex is invalid: %v", err)
	}
	if bytes.Equal(canonical, wantBytes) {
		return
	}
	firstDifference := 0
	for firstDifference < len(canonical) && firstDifference < len(wantBytes) && canonical[firstDifference] == wantBytes[firstDifference] {
		firstDifference++
	}
	t.Fatalf("canonical bytes differ at offset %d (got length %d, want %d)", firstDifference, len(canonical), len(wantBytes))
}

func parseCanonicalUint(t *testing.T, label, raw string) uint64 {
	t.Helper()
	value, err := strconv.ParseUint(raw, 10, 64)
	if err != nil || strconv.FormatUint(value, 10) != raw {
		t.Fatalf("%s = %q is not a canonical uint64: %v", label, raw, err)
	}
	return value
}

type vectorDocument struct {
	SchemaVersion         string                 `json:"schema_version"`
	SlotValueVectors      []slotValueVector      `json:"slot_value_vectors"`
	CommandIDVectors      []commandIDVector      `json:"command_id_vectors"`
	CommandRequestVectors []commandRequestVector `json:"command_request_vectors"`
	EvidenceVectors       []evidenceVector       `json:"evidence_vectors"`
	ULEB128Vectors        []uleb128Vector        `json:"uleb128_vectors"`
	RejectionVectors      []rejectionVector      `json:"rejection_vectors"`
}

type slotValueVector struct {
	Name              string          `json:"name"`
	SlotValue         vectorSlotValue `json:"slot_value"`
	CanonicalBytesHex string          `json:"canonical_bytes_hex"`
	SlotValueDigest   string          `json:"slot_value_digest"`
}

type commandIDVector struct {
	Name      string `json:"name"`
	CommandID string `json:"command_id"`
	Valid     bool   `json:"valid"`
}

type commandRequestVector struct {
	Name              string               `json:"name"`
	CommandID         string               `json:"command_id"`
	Request           vectorCommandRequest `json:"request"`
	CanonicalBytesHex string               `json:"canonical_bytes_hex"`
	RequestDigest     string               `json:"request_digest"`
}

type evidenceVector struct {
	Name              string         `json:"name"`
	Evidence          vectorEvidence `json:"evidence"`
	CanonicalBytesHex string         `json:"canonical_bytes_hex"`
	EvidenceDigest    string         `json:"evidence_digest"`
}

type uleb128Vector struct {
	ValueDecimal string `json:"value_decimal"`
	EncodedHex   string `json:"encoded_hex"`
}

type rejectionVector struct {
	Name          string `json:"name"`
	Target        string `json:"target"`
	BaseVector    string `json:"base_vector"`
	Mutation      string `json:"mutation"`
	ErrorContains string `json:"error_contains"`
}

type vectorBundleKey struct {
	BundleKeyVersion   string `json:"bundle_key_version"`
	SlotOwnerKind      string `json:"slot_owner_kind"`
	SlotOwnerNamespace string `json:"slot_owner_namespace"`
	LocalComponentKey  string `json:"local_component_key"`
}

func (value vectorBundleKey) value() BundleKey {
	return BundleKey{
		BundleKeyVersion:   value.BundleKeyVersion,
		SlotOwnerKind:      layoutbundlekeyv1.SlotOwnerKind(value.SlotOwnerKind),
		SlotOwnerNamespace: value.SlotOwnerNamespace,
		LocalComponentKey:  layoutbundlekeyv1.LocalComponentKey(value.LocalComponentKey),
	}
}

type vectorSlotValue struct {
	ValueVersion string          `json:"value_version"`
	BundleKey    vectorBundleKey `json:"bundle_key"`
	State        string          `json:"state"`
}

func (value vectorSlotValue) value() SystemLayoutBundleSlotV1 {
	return SystemLayoutBundleSlotV1{
		ValueVersion: value.ValueVersion,
		BundleKey:    value.BundleKey.value(),
		State:        SlotState(value.State),
	}
}

type vectorExpectedPrior struct {
	Variant         string `json:"variant"`
	SlotValueDigest string `json:"slot_value_digest"`
}

type vectorCommandRequest struct {
	Operation        string              `json:"operation"`
	TargetBundleKey  vectorBundleKey     `json:"target_bundle_key"`
	DesiredSlotValue vectorSlotValue     `json:"desired_slot_value"`
	ExpectedPrior    vectorExpectedPrior `json:"expected_prior"`
}

func (value vectorCommandRequest) value() SystemLayoutBundleSlotCommandRequest {
	return SystemLayoutBundleSlotCommandRequest{
		Operation:        Operation(value.Operation),
		TargetBundleKey:  value.TargetBundleKey.value(),
		DesiredSlotValue: value.DesiredSlotValue.value(),
		ExpectedPrior: ExpectedPrior{
			Variant:         ExpectedPriorVariant(value.ExpectedPrior.Variant),
			SlotValueDigest: value.ExpectedPrior.SlotValueDigest,
		},
	}
}

type vectorCatalogSnapshot struct {
	OrgID                 string `json:"org_id"`
	CatalogRevision       string `json:"catalog_revision"`
	CatalogSnapshotDigest string `json:"catalog_snapshot_digest"`
	SchemaGeneration      string `json:"schema_generation"`
	EngineGeneration      string `json:"engine_generation"`
}

type vectorEvidence struct {
	EvidenceVersion       string                `json:"evidence_version"`
	SourceCatalogSnapshot vectorCatalogSnapshot `json:"source_catalog_snapshot"`
	BundleKey             vectorBundleKey       `json:"bundle_key"`
	SlotValueVersion      string                `json:"slot_value_version"`
	SlotState             string                `json:"slot_state"`
}

func (value vectorEvidence) value(t *testing.T) SystemLayoutBundleAuthorityEvidenceV1 {
	t.Helper()
	return SystemLayoutBundleAuthorityEvidenceV1{
		EvidenceVersion: value.EvidenceVersion,
		SourceCatalogSnapshot: catalogpublicationv1.CatalogSnapshotBinding{
			OrgID:                 value.SourceCatalogSnapshot.OrgID,
			CatalogRevision:       parseCanonicalUint(t, "catalog_revision", value.SourceCatalogSnapshot.CatalogRevision),
			CatalogSnapshotDigest: value.SourceCatalogSnapshot.CatalogSnapshotDigest,
			SchemaGeneration:      parseCanonicalUint(t, "schema_generation", value.SourceCatalogSnapshot.SchemaGeneration),
			EngineGeneration:      parseCanonicalUint(t, "engine_generation", value.SourceCatalogSnapshot.EngineGeneration),
		},
		BundleKey:        value.BundleKey.value(),
		SlotValueVersion: value.SlotValueVersion,
		SlotState:        SlotState(value.SlotState),
	}
}
