/*
 * Copyright 2025 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package adk

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/cloudwego/eino/schema"
)

// mockRunnerAgent is a simple implementation of the Agent interface for testing Runner
type mockRunnerAgent struct {
	name        string
	description string
	responses   []*AgentEvent
	// Track calls to verify correct parameters were passed
	callCount       int
	lastInput       *AgentInput
	enableStreaming bool
}

func (a *mockRunnerAgent) Name(_ context.Context) string {
	return a.name
}

func (a *mockRunnerAgent) Description(_ context.Context) string {
	return a.description
}

func (a *mockRunnerAgent) Run(_ context.Context, input *AgentInput, _ ...AgentRunOption) *AsyncIterator[*AgentEvent] {
	// Record the call details for verification
	a.callCount++
	a.lastInput = input
	a.enableStreaming = input.EnableStreaming

	iterator, generator := NewAsyncIteratorPair[*AgentEvent]()

	go func() {
		defer generator.Close()

		for _, event := range a.responses {
			generator.Send(event)

			// If the event has an Exit action, stop sending events
			if event.Action != nil && event.Action.Exit {
				break
			}
		}
	}()

	return iterator
}

func newMockRunnerAgent(name, description string, responses []*AgentEvent) *mockRunnerAgent {
	return &mockRunnerAgent{
		name:        name,
		description: description,
		responses:   responses,
	}
}

func TestNewRunner(t *testing.T) {
	ctx := context.Background()
	config := RunnerConfig{}

	runner := NewRunner(ctx, config)

	// Verify that a non-nil runner is returned
	assert.NotNil(t, runner)
}

func TestRunner_Run(t *testing.T) {
	ctx := context.Background()

	// Create a mock agent with predefined responses
	mockAgent_ := newMockRunnerAgent("TestAgent", "Test agent for Runner", []*AgentEvent{
		{
			AgentName: "TestAgent",
			Output: &AgentOutput{
				MessageOutput: &MessageVariant{
					IsStreaming: false,
					Message:     schema.AssistantMessage("Response from test agent", nil),
					Role:        schema.Assistant,
				},
			}},
	})

	// Create a runner
	runner := NewRunner(ctx, RunnerConfig{Agent: mockAgent_})

	// Create test messages
	msgs := []Message{
		schema.UserMessage("Hello, agent!"),
	}

	// Test Run method without streaming
	iterator := runner.Run(ctx, msgs)

	// Verify that the agent's Run method was called with the correct parameters
	assert.Equal(t, 1, mockAgent_.callCount)
	assert.Equal(t, msgs, mockAgent_.lastInput.Messages)
	assert.False(t, mockAgent_.enableStreaming)

	// Verify that we can get the expected response from the iterator
	event, ok := iterator.Next()
	assert.True(t, ok)
	assert.Equal(t, "TestAgent", event.AgentName)
	assert.NotNil(t, event.Output)
	assert.NotNil(t, event.Output.MessageOutput)
	assert.NotNil(t, event.Output.MessageOutput.Message)
	assert.Equal(t, "Response from test agent", event.Output.MessageOutput.Message.Content)

	// Verify that the iterator is now closed
	_, ok = iterator.Next()
	assert.False(t, ok)
}

func TestRunner_Run_WithStreaming(t *testing.T) {
	ctx := context.Background()

	// Create a mock agent with predefined responses
	mockAgent_ := newMockRunnerAgent("TestAgent", "Test agent for Runner", []*AgentEvent{
		{
			AgentName: "TestAgent",
			Output: &AgentOutput{
				MessageOutput: &MessageVariant{
					IsStreaming:   true,
					Message:       nil,
					MessageStream: schema.StreamReaderFromArray([]*schema.Message{schema.AssistantMessage("Streaming response", nil)}),
					Role:          schema.Assistant,
				},
			}},
	})

	// Create a runner
	runner := NewRunner(ctx, RunnerConfig{EnableStreaming: true, Agent: mockAgent_})

	// Create test messages
	msgs := []Message{
		schema.UserMessage("Hello, agent!"),
	}

	// Test Run method with streaming enabled
	iterator := runner.Run(ctx, msgs)

	// Verify that the agent's Run method was called with the correct parameters
	assert.Equal(t, 1, mockAgent_.callCount)
	assert.Equal(t, msgs, mockAgent_.lastInput.Messages)
	assert.True(t, mockAgent_.enableStreaming)

	// Verify that we can get the expected response from the iterator
	event, ok := iterator.Next()
	assert.True(t, ok)
	assert.Equal(t, "TestAgent", event.AgentName)
	assert.NotNil(t, event.Output)
	assert.NotNil(t, event.Output.MessageOutput)
	assert.True(t, event.Output.MessageOutput.IsStreaming)

	// Verify that the iterator is now closed
	_, ok = iterator.Next()
	assert.False(t, ok)
}

func TestRunner_Query(t *testing.T) {
	ctx := context.Background()

	// Create a mock agent with predefined responses
	mockAgent_ := newMockRunnerAgent("TestAgent", "Test agent for Runner", []*AgentEvent{
		{
			AgentName: "TestAgent",
			Output: &AgentOutput{
				MessageOutput: &MessageVariant{
					IsStreaming: false,
					Message:     schema.AssistantMessage("Response to query", nil),
					Role:        schema.Assistant,
				},
			}},
	})

	// Create a runner
	runner := NewRunner(ctx, RunnerConfig{Agent: mockAgent_})

	// Test Query method
	iterator := runner.Query(ctx, "Test query")

	// Verify that the agent's Run method was called with the correct parameters
	assert.Equal(t, 1, mockAgent_.callCount)
	assert.Equal(t, 1, len(mockAgent_.lastInput.Messages))
	assert.Equal(t, "Test query", mockAgent_.lastInput.Messages[0].Content)
	assert.False(t, mockAgent_.enableStreaming)

	// Verify that we can get the expected response from the iterator
	event, ok := iterator.Next()
	assert.True(t, ok)
	assert.Equal(t, "TestAgent", event.AgentName)
	assert.NotNil(t, event.Output)
	assert.NotNil(t, event.Output.MessageOutput)
	assert.NotNil(t, event.Output.MessageOutput.Message)
	assert.Equal(t, "Response to query", event.Output.MessageOutput.Message.Content)

	// Verify that the iterator is now closed
	_, ok = iterator.Next()
	assert.False(t, ok)
}

func TestRunner_Query_WithStreaming(t *testing.T) {
	ctx := context.Background()

	// Create a mock agent with predefined responses
	mockAgent_ := newMockRunnerAgent("TestAgent", "Test agent for Runner", []*AgentEvent{
		{
			AgentName: "TestAgent",
			Output: &AgentOutput{
				MessageOutput: &MessageVariant{
					IsStreaming:   true,
					Message:       nil,
					MessageStream: schema.StreamReaderFromArray([]*schema.Message{schema.AssistantMessage("Streaming query response", nil)}),
					Role:          schema.Assistant,
				},
			}},
	})

	// Create a runner
	runner := NewRunner(ctx, RunnerConfig{EnableStreaming: true, Agent: mockAgent_})

	// Test Query method with streaming enabled
	iterator := runner.Query(ctx, "Test query")

	// Verify that the agent's Run method was called with the correct parameters
	assert.Equal(t, 1, mockAgent_.callCount)
	assert.Equal(t, 1, len(mockAgent_.lastInput.Messages))
	assert.Equal(t, "Test query", mockAgent_.lastInput.Messages[0].Content)
	assert.True(t, mockAgent_.enableStreaming)

	// Verify that we can get the expected response from the iterator
	event, ok := iterator.Next()
	assert.True(t, ok)
	assert.Equal(t, "TestAgent", event.AgentName)
	assert.NotNil(t, event.Output)
	assert.NotNil(t, event.Output.MessageOutput)
	assert.True(t, event.Output.MessageOutput.IsStreaming)

	// Verify that the iterator is now closed
	_, ok = iterator.Next()
	assert.False(t, ok)
}

func TestResumeWithMissingCheckpoint(t *testing.T) {
	ctx := context.Background()

	agent := &myAgenticAgent{
		name: "resume-agent",
		runFn: func(ctx context.Context, input *TypedAgentInput[*schema.AgenticMessage], options ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[*schema.AgenticMessage]] {
			iter, gen := NewAsyncIteratorPair[*TypedAgentEvent[*schema.AgenticMessage]]()
			go func() {
				defer gen.Close()
				gen.Send(&TypedAgentEvent[*schema.AgenticMessage]{
					Output: &TypedAgentOutput[*schema.AgenticMessage]{
						MessageOutput: &TypedMessageVariant[*schema.AgenticMessage]{
							Message: agenticMsg("ok"),
						},
					},
				})
			}()
			return iter
		},
	}

	store := newMyStore()
	runner := NewTypedRunner(TypedRunnerConfig[*schema.AgenticMessage]{
		Agent:           agent,
		CheckPointStore: store,
	})

	require.NotPanics(t, func() {
		iter, err := runner.ResumeWithParams(ctx, "nonexistent-checkpoint", &ResumeParams{
			Targets: map[string]any{"fake-id": nil},
		})
		if err != nil {
			t.Logf("Got expected error: %v", err)
			return
		}
		for {
			event, ok := iter.Next()
			if !ok {
				break
			}
			if event.Err != nil {
				t.Logf("Got error event: %v", event.Err)
			}
		}
	}, "ResumeWithParams with nonexistent checkpoint should not panic")
}
