package adk

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/orchard9/stemedb-go/steme"
)

// mockClient implements EpistemeClient for testing
type mockClient struct {
	queryFunc     func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error)
	assertFunc    func(ctx context.Context, assertion steme.Assertion) (string, error)
	traceFunc     func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error)
	supersedeFunc func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error)
}

func (m *mockClient) Query(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
	if m.queryFunc != nil {
		return m.queryFunc(ctx, params)
	}
	return &steme.QueryResult{}, nil
}

func (m *mockClient) Assert(ctx context.Context, assertion steme.Assertion) (string, error) {
	if m.assertFunc != nil {
		return m.assertFunc(ctx, assertion)
	}
	return "mock_hash", nil
}

func (m *mockClient) Trace(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) {
	if m.traceFunc != nil {
		return m.traceFunc(ctx, params)
	}
	return &steme.TraceResult{}, nil
}

func (m *mockClient) Supersede(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) {
	if m.supersedeFunc != nil {
		return m.supersedeFunc(ctx, params)
	}
	return &steme.SupersedeResult{
		Status:           "superseded",
		TargetHash:       params.TargetHash,
		SupersessionType: params.SupersessionType,
		Timestamp:        1704067200,
	}, nil
}

func TestQueryTool(t *testing.T) {
	// Mock client that returns a test assertion
	client := &mockClient{
		queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
			lifecycle := steme.LifecycleApproved
			return &steme.QueryResult{
				Assertions: []steme.AssertionResponse{
					{
						Hash:       "test_hash",
						Subject:    "Tesla_Inc",
						Predicate:  "has_revenue",
						Object:     steme.NewNumberValue(96.7),
						Confidence: 0.95,
						SourceHash: "source_hash",
						Lifecycle:  lifecycle,
					},
				},
				TotalCount: 1,
				HasMore:    false,
			}, nil
		},
	}

	tool := NewQueryTool(client)

	// Test basic query
	input := QueryInput{
		Subject:   "Tesla_Inc",
		Predicate: "has_revenue",
		Lens:      "consensus",
	}

	inputBytes, err := json.Marshal(input)
	if err != nil {
		t.Fatalf("failed to marshal input: %v", err)
	}

	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("query failed: %v", err)
	}

	var output QueryOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Verify output
	if output.Confidence != 0.95 {
		t.Errorf("expected confidence 0.95, got %f", output.Confidence)
	}

	if output.Lifecycle != "Approved" {
		t.Errorf("expected lifecycle Approved, got %s", output.Lifecycle)
	}

	if output.QueryID == "" {
		t.Error("expected non-empty query ID")
	}

	if len(output.Sources) == 0 {
		t.Error("expected at least one source")
	}
}

func TestQueryToolConfidenceThreshold(t *testing.T) {
	// Mock client that returns low confidence
	client := &mockClient{
		queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
			lifecycle := steme.LifecycleApproved
			return &steme.QueryResult{
				Assertions: []steme.AssertionResponse{
					{
						Hash:       "test_hash",
						Subject:    "test",
						Predicate:  "test",
						Object:     steme.NewTextValue("test"),
						Confidence: 0.6, // Low confidence
						SourceHash: "source_hash",
						Lifecycle:  lifecycle,
					},
				},
				TotalCount: 1,
			}, nil
		},
	}

	tool := NewQueryTool(client)

	input := QueryInput{
		Subject:       "test",
		Predicate:     "test",
		MinConfidence: 0.8, // Require high confidence
	}

	inputBytes, _ := json.Marshal(input)
	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("query failed: %v", err)
	}

	var output QueryOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Should have error about low confidence
	if output.Error == "" {
		t.Error("expected error for low confidence")
	}
}

func TestAssertTool(t *testing.T) {
	// Track what assertion was created
	var capturedAssertion steme.Assertion

	client := &mockClient{
		assertFunc: func(ctx context.Context, assertion steme.Assertion) (string, error) {
			capturedAssertion = assertion
			return "created_hash", nil
		},
	}

	tool := NewAssertTool(client)

	input := AssertInput{
		Subject:    "Tesla_Inc",
		Predicate:  "has_revenue",
		Object:     96.7,
		SourceHash: "0000000000000000000000000000000000000000000000000000000000000000",
		Confidence: 0.95,
		Lifecycle:  "approved",
	}

	inputBytes, _ := json.Marshal(input)
	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("assert failed: %v", err)
	}

	var output AssertOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Verify output
	if !output.Success {
		t.Errorf("expected success, got error: %s", output.Error)
	}

	if output.Hash != "created_hash" {
		t.Errorf("expected hash created_hash, got %s", output.Hash)
	}

	// Verify captured assertion
	if capturedAssertion.Subject != "Tesla_Inc" {
		t.Errorf("expected subject Tesla_Inc, got %s", capturedAssertion.Subject)
	}

	// Use approximate comparison for floating point
	if capturedAssertion.Confidence < 0.94 || capturedAssertion.Confidence > 0.96 {
		t.Errorf("expected confidence ~0.95, got %f", capturedAssertion.Confidence)
	}
}

func TestConstraintCheckTool(t *testing.T) {
	// Mock client that returns constraint assertions
	client := &mockClient{
		queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
			lifecycle := steme.LifecycleApproved
			return &steme.QueryResult{
				Assertions: []steme.AssertionResponse{
					{
						Hash:       "constraint1",
						Subject:    "auth_jwt",
						Predicate:  "must_use",
						Object:     steme.NewTextValue("Ed25519"),
						Confidence: 1.0,
						SourceHash: "source",
						Lifecycle:  lifecycle,
					},
					{
						Hash:       "constraint2",
						Subject:    "auth_jwt",
						Predicate:  "forbidden",
						Object:     steme.NewTextValue("MD5"),
						Confidence: 1.0,
						SourceHash: "source",
						Lifecycle:  lifecycle,
					},
				},
				TotalCount: 2,
			}, nil
		},
	}

	tool := NewConstraintCheckTool(client)

	input := ConstraintCheckInput{
		Context: "auth_jwt",
	}

	inputBytes, _ := json.Marshal(input)
	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("constraint check failed: %v", err)
	}

	var output ConstraintCheckOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Verify constraints were returned
	if len(output.Constraints) != 2 {
		t.Errorf("expected 2 constraints, got %d", len(output.Constraints))
	}

	// Check for must-use constraint
	foundMustUse := false
	for _, c := range output.Constraints {
		if c.MustUse == "Ed25519" {
			foundMustUse = true
		}
	}
	if !foundMustUse {
		t.Error("expected to find Ed25519 must-use constraint")
	}

	// Check for forbidden constraint
	foundForbidden := false
	for _, c := range output.Constraints {
		if c.Forbidden == "MD5" {
			foundForbidden = true
		}
	}
	if !foundForbidden {
		t.Error("expected to find MD5 forbidden constraint")
	}
}

func TestTraceTool(t *testing.T) {
	// Mock client that returns audit records
	agentID := "deadbeef00000000000000000000000000000000000000000000000000000000"
	subject := "Tesla_Inc"
	predicate := "has_revenue"
	resultHash := "result_hash_123"
	lens := steme.LensConsensus

	client := &mockClient{
		traceFunc: func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) {
			return &steme.TraceResult{
				Audits: []steme.QueryAuditRecord{
					{
						QueryID:   "query_123",
						AgentID:   &agentID,
						Timestamp: 1704067200,
						Params: steme.QueryParamsAudit{
							Subject:   &subject,
							Predicate: &predicate,
							Lens:      &lens,
						},
						ResultHash:       &resultHash,
						ResultConfidence: 0.95,
						ContributingAssertions: []steme.ContributingAssertion{
							{
								AssertionHash: "assertion_1",
								Weight:        1.0,
								SourceHash:    "source_1",
								Lifecycle:     steme.LifecycleApproved,
							},
							{
								AssertionHash: "assertion_2",
								Weight:        0.5,
								SourceHash:    "source_2",
								Lifecycle:     steme.LifecycleProposed,
							},
						},
					},
				},
				TotalCount:    1,
				AgentID:       agentID,
				FromTimestamp: 1704067200,
				ToTimestamp:   1704153600,
			}, nil
		},
	}

	tool := NewTraceTool(client)

	input := TraceInput{
		AgentID: agentID,
		From:    "1704067200",
		To:      "1704153600",
		Subject: "Tesla*",
	}

	inputBytes, _ := json.Marshal(input)
	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("trace failed: %v", err)
	}

	var output TraceOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Verify output
	if len(output.Queries) != 1 {
		t.Errorf("expected 1 query, got %d", len(output.Queries))
	}

	if len(output.Queries) > 0 {
		query := output.Queries[0]

		if query.QueryID != "query_123" {
			t.Errorf("expected query_id query_123, got %s", query.QueryID)
		}

		if query.Subject != "Tesla_Inc" {
			t.Errorf("expected subject Tesla_Inc, got %s", query.Subject)
		}

		if query.Predicate != "has_revenue" {
			t.Errorf("expected predicate has_revenue, got %s", query.Predicate)
		}

		if query.Lens != "consensus" {
			t.Errorf("expected lens consensus, got %s", query.Lens)
		}

		if query.Confidence != 0.95 {
			t.Errorf("expected confidence 0.95, got %f", query.Confidence)
		}

		if len(query.Contributing) != 2 {
			t.Errorf("expected 2 contributing assertions, got %d", len(query.Contributing))
		}
	}
}

func TestSupersedeTool(t *testing.T) {
	// Track what supersession was created
	var capturedParams steme.SupersedeParams

	client := &mockClient{
		supersedeFunc: func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) {
			capturedParams = params
			return &steme.SupersedeResult{
				Status:           "superseded",
				TargetHash:       params.TargetHash,
				SupersessionType: params.SupersessionType,
				Timestamp:        1704067200,
			}, nil
		},
	}

	tool := NewSupersedeTool(client)

	input := SupersedeInput{
		Hash:    "abc123def456",
		Type:    "Invalidate",
		Reason:  "Proposal treated as approved. See incident INC-2024-001",
		NewHash: "def456abc123",
		AgentID: "deadbeef00000000000000000000000000000000000000000000000000000000",
		Signature: "0000000000000000000000000000000000000000000000000000000000000000" +
			"0000000000000000000000000000000000000000000000000000000000000000",
	}

	inputBytes, err := json.Marshal(input)
	if err != nil {
		t.Fatalf("failed to marshal input: %v", err)
	}

	outputBytes, err := tool.Execute(context.Background(), inputBytes)
	if err != nil {
		t.Fatalf("supersede failed: %v", err)
	}

	var output SupersedeOutput
	if err := json.Unmarshal(outputBytes, &output); err != nil {
		t.Fatalf("failed to unmarshal output: %v", err)
	}

	// Verify output
	if !output.Success {
		t.Errorf("expected success, got error: %s", output.Error)
	}

	if output.TargetHash != "abc123def456" {
		t.Errorf("expected target hash abc123def456, got %s", output.TargetHash)
	}

	if output.SupersessionType != "Invalidate" {
		t.Errorf("expected supersession type Invalidate, got %s", output.SupersessionType)
	}

	// Verify captured params
	if capturedParams.TargetHash != "abc123def456" {
		t.Errorf("expected target hash abc123def456, got %s", capturedParams.TargetHash)
	}

	if capturedParams.SupersessionType != steme.SupersessionInvalidate {
		t.Errorf("expected supersession type Invalidate, got %s", capturedParams.SupersessionType)
	}

	if capturedParams.Reason != "Proposal treated as approved. See incident INC-2024-001" {
		t.Errorf("expected reason about proposal, got %s", capturedParams.Reason)
	}
}

func TestParseSupersessionType(t *testing.T) {
	tests := []struct {
		input    string
		expected steme.SupersessionType
	}{
		{"Invalidate", steme.SupersessionInvalidate},
		{"invalidate", steme.SupersessionInvalidate},
		{"Temporal", steme.SupersessionTemporal},
		{"temporal", steme.SupersessionTemporal},
		{"Refinement", steme.SupersessionRefinement},
		{"RequiresReview", steme.SupersessionRequiresReview},
		{"requires_review", steme.SupersessionRequiresReview},
		{"Additive", steme.SupersessionAdditive},
		{"additive", steme.SupersessionAdditive},
		{"unknown", steme.SupersessionInvalidate}, // Default
	}

	for _, tt := range tests {
		result := parseSupersessionType(tt.input)
		if result != tt.expected {
			t.Errorf("parseSupersessionType(%s) = %v, expected %v", tt.input, result, tt.expected)
		}
	}
}

func TestToolNames(t *testing.T) {
	client := &mockClient{}

	tests := []struct {
		tool         Tool
		expectedName string
	}{
		{NewQueryTool(client), "episteme_query"},
		{NewAssertTool(client), "episteme_assert"},
		{NewConstraintCheckTool(client), "episteme_constraint_check"},
		{NewTraceTool(client), "episteme_trace"},
		{NewSupersedeTool(client), "episteme_supersede"},
	}

	for _, tt := range tests {
		if tt.tool.Name() != tt.expectedName {
			t.Errorf("expected tool name %s, got %s", tt.expectedName, tt.tool.Name())
		}
	}
}

func TestAllTools(t *testing.T) {
	client := &mockClient{}
	tools := AllTools(client)

	if len(tools) != 5 {
		t.Errorf("expected 5 tools, got %d", len(tools))
	}

	// Verify all expected tools are present
	expectedNames := map[string]bool{
		"episteme_query":            false,
		"episteme_assert":           false,
		"episteme_constraint_check": false,
		"episteme_trace":            false,
		"episteme_supersede":        false,
	}

	for _, tool := range tools {
		expectedNames[tool.Name()] = true
	}

	for name, found := range expectedNames {
		if !found {
			t.Errorf("expected to find tool %s", name)
		}
	}
}

func TestConfigCreation(t *testing.T) {
	client := &mockClient{}
	logFunc := func(format string, args ...any) {}
	setState := func(key string, value any) {}

	configs := AllConfigs(client, 0.8, setState, logFunc)

	expectedAgents := []string{
		"implementation",
		"lead",
		"research",
		"supervisor",
		"oncall",
	}

	for _, agentType := range expectedAgents {
		config, ok := configs[agentType]
		if !ok {
			t.Errorf("expected config for %s", agentType)
			continue
		}

		if config.Name == "" {
			t.Errorf("config for %s has empty name", agentType)
		}

		if config.Description == "" {
			t.Errorf("config for %s has empty description", agentType)
		}

		if config.Instruction == "" {
			t.Errorf("config for %s has empty instruction", agentType)
		}

		if len(config.Tools) == 0 {
			t.Errorf("config for %s has no tools", agentType)
		}
	}
}

func TestParseLifecycle(t *testing.T) {
	tests := []struct {
		input    string
		expected steme.LifecycleStage
	}{
		{"proposed", steme.LifecycleProposed},
		{"under_review", steme.LifecycleUnderReview},
		{"approved", steme.LifecycleApproved},
		{"deprecated", steme.LifecycleDeprecated},
		{"rejected", steme.LifecycleRejected},
		{"invalid", steme.LifecycleProposed}, // Default
	}

	for _, tt := range tests {
		result := parseLifecycle(tt.input)
		if result != tt.expected {
			t.Errorf("parseLifecycle(%s) = %v, expected %v", tt.input, result, tt.expected)
		}
	}
}

func TestParseLens(t *testing.T) {
	tests := []struct {
		input    string
		expected steme.Lens
	}{
		{"recency", steme.LensRecency},
		{"consensus", steme.LensConsensus},
		{"authority", steme.LensAuthority},
		{"vote_aware_consensus", steme.LensVoteAwareConsensus},
		{"trust_aware_authority", steme.LensTrustAwareAuthority},
		{"invalid", steme.LensConsensus}, // Default
	}

	for _, tt := range tests {
		result := parseLen(tt.input)
		if result != tt.expected {
			t.Errorf("parseLen(%s) = %v, expected %v", tt.input, result, tt.expected)
		}
	}
}
