package steme

import (
	"encoding/hex"
	"testing"
)

// Note: context is used in the skipped integration test

// TestSignerGeneration tests keypair generation and serialization.
func TestSignerGeneration(t *testing.T) {
	signer, err := GenerateSigner()
	if err != nil {
		t.Fatalf("GenerateSigner() failed: %v", err)
	}

	// Check public key is 64 hex chars (32 bytes)
	pubKey := signer.PublicKey()
	if len(pubKey) != 64 {
		t.Errorf("PublicKey() length = %d, want 64", len(pubKey))
	}

	// Check seed is 64 hex chars (32 bytes)
	seed := signer.Seed()
	if len(seed) != 64 {
		t.Errorf("Seed() length = %d, want 64", len(seed))
	}

	// Check we can reconstruct signer from seed
	signer2, err := NewSignerFromHex(seed)
	if err != nil {
		t.Fatalf("NewSignerFromHex() failed: %v", err)
	}

	if signer.PublicKey() != signer2.PublicKey() {
		t.Errorf("PublicKey mismatch after reconstruction")
	}
}

// TestSignerSignAndVerify tests signature creation and verification.
func TestSignerSignAndVerify(t *testing.T) {
	signer, err := GenerateSigner()
	if err != nil {
		t.Fatalf("GenerateSigner() failed: %v", err)
	}

	message := []byte("test message")
	signature := signer.Sign(message)

	// Signature should be 128 hex chars (64 bytes)
	if len(signature) != 128 {
		t.Errorf("Sign() signature length = %d, want 128", len(signature))
	}

	// Verify the signature
	err = Verify(signer.PublicKey(), signature, message)
	if err != nil {
		t.Errorf("Verify() failed: %v", err)
	}

	// Verify fails with wrong message
	err = Verify(signer.PublicKey(), signature, []byte("wrong message"))
	if err == nil {
		t.Errorf("Verify() should fail with wrong message")
	}
}

// TestAssertionBuilder tests the fluent assertion builder API.
func TestAssertionBuilder(t *testing.T) {
	lifecycle := LifecycleApproved
	sourceClass := SourceClassClinical

	assertion := NewAssertion("Tesla_Inc", "has_revenue").
		WithNumber(96.7).
		WithConfidence(0.95).
		WithLifecycle(lifecycle).
		WithSourceClass(sourceClass).
		WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
		Build()

	if assertion.Subject != "Tesla_Inc" {
		t.Errorf("Subject = %s, want Tesla_Inc", assertion.Subject)
	}

	if assertion.Predicate != "has_revenue" {
		t.Errorf("Predicate = %s, want has_revenue", assertion.Predicate)
	}

	if assertion.Object.Type != "Number" {
		t.Errorf("Object.Type = %s, want Number", assertion.Object.Type)
	}

	if assertion.Object.Value != 96.7 {
		t.Errorf("Object.Value = %v, want 96.7", assertion.Object.Value)
	}

	if assertion.Confidence != 0.95 {
		t.Errorf("Confidence = %f, want 0.95", assertion.Confidence)
	}

	if assertion.Lifecycle == nil || *assertion.Lifecycle != LifecycleApproved {
		t.Errorf("Lifecycle = %v, want Approved", assertion.Lifecycle)
	}

	if assertion.SourceClass == nil || *assertion.SourceClass != SourceClassClinical {
		t.Errorf("SourceClass = %v, want Clinical", assertion.SourceClass)
	}
}

// TestAssertionValidation tests assertion validation.
func TestAssertionValidation(t *testing.T) {
	tests := []struct {
		name    string
		build   func() Assertion
		wantErr bool
	}{
		{
			name: "valid assertion",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					WithConfidence(0.95).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr: false,
		},
		{
			name: "confidence too high",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					WithConfidence(1.5).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr: true,
		},
		{
			name: "confidence negative",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					WithConfidence(-0.1).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr: true,
		},
		{
			name: "missing source_hash",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					Build()
			},
			wantErr: true,
		},
		{
			name: "invalid source_hash length",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					WithSourceHash("00").
					Build()
			},
			wantErr: true,
		},
		{
			name: "invalid source_hash hex",
			build: func() Assertion {
				return NewAssertion("Tesla_Inc", "has_revenue").
					WithNumber(96.7).
					WithSourceHash("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz").
					Build()
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assertion := tt.build()
			err := assertion.Validate()

			if (err != nil) != tt.wantErr {
				t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

// TestQueryBuilder tests the fluent query builder API.
func TestQueryBuilder(t *testing.T) {
	lens := LensConsensus
	lifecycle := LifecycleApproved

	params := NewQuery().
		WithSubject("Tesla_Inc").
		WithPredicate("has_revenue").
		WithLens(lens).
		WithLifecycle(lifecycle).
		WithLimit(10).
		Build()

	if params.Subject == nil || *params.Subject != "Tesla_Inc" {
		t.Errorf("Subject = %v, want Tesla_Inc", params.Subject)
	}

	if params.Predicate == nil || *params.Predicate != "has_revenue" {
		t.Errorf("Predicate = %v, want has_revenue", params.Predicate)
	}

	if params.Lens == nil || *params.Lens != LensConsensus {
		t.Errorf("Lens = %v, want Consensus", params.Lens)
	}

	if params.Lifecycle == nil || *params.Lifecycle != LifecycleApproved {
		t.Errorf("Lifecycle = %v, want Approved", params.Lifecycle)
	}

	if params.Limit != 10 {
		t.Errorf("Limit = %d, want 10", params.Limit)
	}
}

// TestCanonicalMessage tests the canonical message generation for signing.
func TestCanonicalMessage(t *testing.T) {
	assertion := NewAssertion("Tesla_Inc", "has_revenue").
		WithNumber(96.7).
		WithConfidence(0.95).
		WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
		Build()

	msg1, err := canonicalAssertionMessage(&assertion)
	if err != nil {
		t.Fatalf("canonicalAssertionMessage() failed: %v", err)
	}

	// Verify format matches server expectation: "{subject}:{predicate}"
	expected := "Tesla_Inc:has_revenue"
	if string(msg1) != expected {
		t.Errorf("canonicalAssertionMessage() = %q, want %q", string(msg1), expected)
	}

	// Same assertion should produce same message
	msg2, err := canonicalAssertionMessage(&assertion)
	if err != nil {
		t.Fatalf("canonicalAssertionMessage() failed: %v", err)
	}

	if string(msg1) != string(msg2) {
		t.Errorf("Canonical message not deterministic")
	}

	// Different subject/predicate should produce different message
	assertion2 := NewAssertion("Tesla_Inc", "has_employees"). // Different predicate
									WithNumber(97.0).
									WithConfidence(0.95).
									WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
									Build()

	msg3, err := canonicalAssertionMessage(&assertion2)
	if err != nil {
		t.Fatalf("canonicalAssertionMessage() failed: %v", err)
	}

	if string(msg1) == string(msg3) {
		t.Errorf("Different assertions produced same canonical message")
	}
}

// TestClientIntegration is a placeholder for integration tests.
//
// This would test against a real StemeDB server. For now, it's skipped
// unless the STEMEDB_URL environment variable is set.
func TestClientIntegration(t *testing.T) {
	t.Skip("Integration test requires running StemeDB server")

	// Example integration test structure:
	/*
		baseURL := os.Getenv("STEMEDB_URL")
		if baseURL == "" {
			t.Skip("STEMEDB_URL not set")
		}

		signer, err := GenerateSigner()
		if err != nil {
			t.Fatalf("GenerateSigner() failed: %v", err)
		}

		client := NewClient(baseURL, signer)

		// Test health check
		health, err := client.Health(context.Background())
		if err != nil {
			t.Fatalf("Health() failed: %v", err)
		}

		if health.Status != "healthy" {
			t.Errorf("Health.Status = %s, want healthy", health.Status)
		}

		// Test assertion creation
		assertion := NewAssertion("test_entity", "test_predicate").
			WithText("test value").
			WithConfidence(0.95).
			WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
			Build()

		hash, err := client.Assert(context.Background(), assertion)
		if err != nil {
			t.Fatalf("Assert() failed: %v", err)
		}

		if len(hash) != 64 {
			t.Errorf("Assert() hash length = %d, want 64", len(hash))
		}

		// Test query
		params := NewQuery().
			WithSubject("test_entity").
			WithPredicate("test_predicate").
			Build()

		result, err := client.Query(context.Background(), params)
		if err != nil {
			t.Fatalf("Query() failed: %v", err)
		}

		if result.TotalCount == 0 {
			t.Errorf("Query() returned no results")
		}
	*/
}

// TestSignerFromEnvNotSet tests that SignerFromEnv fails when var is not set.
func TestSignerFromEnvNotSet(t *testing.T) {
	_, err := SignerFromEnv("NONEXISTENT_STEME_VAR_12345")
	if err == nil {
		t.Error("SignerFromEnv() should fail when env var is not set")
	}
}

// TestCanonicalMessageAllObjectTypes tests canonical message with all object types.
// The canonical message is just "{subject}:{predicate}" - object type doesn't affect it.
func TestCanonicalMessageAllObjectTypes(t *testing.T) {
	types := []struct {
		name string
		obj  ObjectValue
	}{
		{"text", NewTextValue("hello")},
		{"number", NewNumberValue(42.5)},
		{"boolean", NewBooleanValue(true)},
		{"reference", NewReferenceValue("Entity_123")},
	}

	for _, tt := range types {
		t.Run(tt.name, func(t *testing.T) {
			a := NewAssertion("Subject", "Predicate").
				WithConfidence(0.5).
				WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
				Build()
			a.Object = tt.obj

			msg, err := canonicalAssertionMessage(&a)
			if err != nil {
				t.Errorf("canonicalAssertionMessage() failed for %s: %v", tt.name, err)
			}
			// Canonical message is "{subject}:{predicate}" regardless of object type
			expected := "Subject:Predicate"
			if string(msg) != expected {
				t.Errorf("Expected %q, got %q", expected, string(msg))
			}
		})
	}
}

// TestCanonicalMessageEdgeCases tests edge cases in canonical message generation.
func TestCanonicalMessageEdgeCases(t *testing.T) {
	tests := []struct {
		name     string
		build    func() Assertion
		wantErr  bool
		expected string
	}{
		{
			name: "zero confidence",
			build: func() Assertion {
				return NewAssertion("S", "P").
					WithText("v").
					WithConfidence(0.0).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr:  false,
			expected: "S:P",
		},
		{
			name: "max confidence",
			build: func() Assertion {
				return NewAssertion("S", "P").
					WithText("v").
					WithConfidence(1.0).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr:  false,
			expected: "S:P",
		},
		{
			name: "empty strings",
			build: func() Assertion {
				return NewAssertion("", "").
					WithText("v").
					WithConfidence(0.5).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr:  false,
			expected: ":",
		},
		{
			name: "special characters",
			build: func() Assertion {
				return NewAssertion("Subject:With:Colons", "Predicate").
					WithText("v").
					WithConfidence(0.5).
					WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
					Build()
			},
			wantErr:  false,
			expected: "Subject:With:Colons:Predicate",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			a := tt.build()
			msg, err := canonicalAssertionMessage(&a)
			if (err != nil) != tt.wantErr {
				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
			}
			if !tt.wantErr && string(msg) != tt.expected {
				t.Errorf("got %q, want %q", string(msg), tt.expected)
			}
		})
	}
}

// TestNewSignerInvalidSeed tests that NewSigner fails with wrong seed size.
func TestNewSignerInvalidSeed(t *testing.T) {
	tests := []struct {
		name    string
		seedLen int
		wantErr bool
	}{
		{"empty seed", 0, true},
		{"short seed", 16, true},
		{"correct seed", 32, false},
		{"long seed", 64, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			seed := make([]byte, tt.seedLen)
			_, err := NewSigner(seed)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

// TestVerifyInvalidInputs tests Verify with invalid inputs.
func TestVerifyInvalidInputs(t *testing.T) {
	signer, _ := GenerateSigner()
	message := []byte("test")
	validSig := signer.Sign(message)

	tests := []struct {
		name    string
		pubKey  string
		sig     string
		wantErr bool
	}{
		{"valid", signer.PublicKey(), validSig, false},
		{"invalid pubkey hex", "zzzz", validSig, true},
		{"short pubkey", "abcd", validSig, true},
		{"invalid sig hex", signer.PublicKey(), "zzzz", true},
		{"short sig", signer.PublicKey(), "abcd", true},
		{"wrong sig", signer.PublicKey(), hex.EncodeToString(make([]byte, 64)), true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := Verify(tt.pubKey, tt.sig, message)
			if (err != nil) != tt.wantErr {
				t.Errorf("Verify() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

// TestObjectValueConstructors tests the object value constructor functions.
func TestObjectValueConstructors(t *testing.T) {
	tests := []struct {
		name      string
		value     ObjectValue
		wantType  string
		wantValue any
	}{
		{
			name:      "text value",
			value:     NewTextValue("hello"),
			wantType:  "Text",
			wantValue: "hello",
		},
		{
			name:      "number value",
			value:     NewNumberValue(42.5),
			wantType:  "Number",
			wantValue: 42.5,
		},
		{
			name:      "boolean value",
			value:     NewBooleanValue(true),
			wantType:  "Boolean",
			wantValue: true,
		},
		{
			name:      "reference value",
			value:     NewReferenceValue("Entity_Id"),
			wantType:  "Reference",
			wantValue: "Entity_Id",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.value.Type != tt.wantType {
				t.Errorf("Type = %s, want %s", tt.value.Type, tt.wantType)
			}

			if tt.value.Value != tt.wantValue {
				t.Errorf("Value = %v, want %v", tt.value.Value, tt.wantValue)
			}
		})
	}
}
