package errcontract

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"
)

// TestGeneratedArtifactMatchesVocabulary is the bidirectional drift test: it
// re-derives the contract in-memory and compares it against the committed
// generated JSON artifact. A missing, invented, or reclassified code, stage,
// severity, or budget value fails here, so the Web projector can never drift
// from the real server vocabulary without a failing test.
func TestGeneratedArtifactMatchesVocabulary(t *testing.T) {
	artifactPath := filepath.Join("..", "..", "..", "web", "src", "agentteam", "workflow", "shared", "workflowErrorContract.generated.json")
	raw, err := os.ReadFile(artifactPath)
	if err != nil {
		t.Fatalf("read generated artifact: %v", err)
	}
	var committed Contract
	if err := json.Unmarshal(raw, &committed); err != nil {
		t.Fatalf("unmarshal generated artifact: %v", err)
	}
	derived := BuildContract()
	// Semantic comparison: the committed artifact must equal the freshly derived
	// contract. A missing, invented, or reclassified code/stage/severity/budget
	// value fails here.
	derivedJSON, err := json.Marshal(derived)
	if err != nil {
		t.Fatalf("marshal derived contract: %v", err)
	}
	committedJSON, err := json.Marshal(committed)
	if err != nil {
		t.Fatalf("marshal committed contract: %v", err)
	}
	if string(derivedJSON) != string(committedJSON) {
		t.Fatalf("generated artifact is stale or drifted:\n--- derived ---\n%s\n--- committed ---\n%s", derivedJSON, committedJSON)
	}
	if committed.Version != Version {
		t.Fatalf("artifact version %q != current %q; regenerate", committed.Version, Version)
	}
}

// TestStageAndSeverityClosed ensures the artifact carries only the closed stage
// and severity vocabularies.
func TestStageAndSeverityClosed(t *testing.T) {
	contract := BuildContract()
	if len(contract.Stages) != 5 {
		t.Fatalf("expected 5 stages, got %d", len(contract.Stages))
	}
	if len(contract.Severities) != 2 {
		t.Fatalf("expected 2 severities (error|warning), got %d", len(contract.Severities))
	}
}

// TestBudgetsMatchServer ensures the artifact budgets mirror the server
// publicerr constants.
func TestBudgetsMatchServer(t *testing.T) {
	b := BuildContract().Budgets
	if b.MaxServerDiagnostics != 100 {
		t.Fatalf("MaxServerDiagnostics must be 100, got %d", b.MaxServerDiagnostics)
	}
	if b.MaxWorkflowErrorBodyBytes != 60*1024 {
		t.Fatalf("MaxWorkflowErrorBodyBytes must be 60KiB, got %d", b.MaxWorkflowErrorBodyBytes)
	}
	if b.MaxDiagnosticsSerializedBytes != 32*1024 {
		t.Fatalf("MaxDiagnosticsSerializedBytes must be 32KiB, got %d", b.MaxDiagnosticsSerializedBytes)
	}
}

// TestMutationInventedOrMissingCodeFails proves the bidirectional drift test is
// sensitive to a missing, invented, or reclassified value: mutating the derived
// vocabulary and comparing against the committed artifact must fail.
func TestMutationInventedOrMissingCodeFails(t *testing.T) {
	artifactPath := filepath.Join("..", "..", "..", "web", "src", "agentteam", "workflow", "shared", "workflowErrorContract.generated.json")
	raw, err := os.ReadFile(artifactPath)
	if err != nil {
		t.Fatalf("read generated artifact: %v", err)
	}
	var committed Contract
	if err := json.Unmarshal(raw, &committed); err != nil {
		t.Fatalf("unmarshal generated artifact: %v", err)
	}
	derived := BuildContract()
	committedJSON, _ := json.Marshal(committed)

	// Mutate: invent a new manage code.
	mutated := derived
	mutated.ManageCodes = append(append([]string(nil), derived.ManageCodes...), "invented.code")
	mutatedJSON, _ := json.Marshal(mutated)
	if string(mutatedJSON) == string(committedJSON) {
		t.Fatalf("inventing a manage code must fail the drift comparison")
	}

	// Mutate: drop a real runtime failure code.
	mutated = derived
	mutated.RuntimeFailureCodes = derived.RuntimeFailureCodes[:len(derived.RuntimeFailureCodes)-1]
	mutatedJSON, _ = json.Marshal(mutated)
	if string(mutatedJSON) == string(committedJSON) {
		t.Fatalf("dropping a runtime failure code must fail the drift comparison")
	}

	// Mutate: reclassify a stage.
	mutated = derived
	mutated.Stages = append(append([]string(nil), derived.Stages...), "invented_stage")
	mutatedJSON, _ = json.Marshal(mutated)
	if string(mutatedJSON) == string(committedJSON) {
		t.Fatalf("inventing a stage must fail the drift comparison")
	}
}
