package assistant

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/neo/store"
	"github.com/yaoapp/yao/test"
)

func prepare(t *testing.T) {
	test.Prepare(t, config.Conf)
}

func TestLoad_LoadPath(t *testing.T) {
	prepare(t)
	defer test.Clean()

	assistant, err := LoadPath("/assistants/modi")
	if err != nil {
		t.Fatal(err)
	}

	// Validate basic properties
	assert.NotNil(t, assistant)
	assert.Equal(t, "modi", assistant.ID)
	assert.Equal(t, "Modi", assistant.Name)
	assert.Equal(t, "https://api.dicebear.com/7.x/bottts/svg?seed=Modi", assistant.Avatar)
	assert.Equal(t, "deepseek", assistant.Connector)
	assert.NotNil(t, assistant.Prompts)
	assert.NotNil(t, assistant.Script)

	// Test non-existent assistant
	_, err = LoadPath("/assistants/non-existent")
	assert.Error(t, err)
}

func TestLoad_LoadStore(t *testing.T) {
	prepare(t)
	defer test.Clean()

	// Test with nil storage
	_, err := LoadStore("test-id")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "storage is not set")

	// Setup mock storage
	mockStore := &mockStore{
		data: map[string]map[string]interface{}{
			"test-id": {
				"assistant_id": "test-id",
				"name":         "Test Assistant",
				"avatar":       "test-avatar",
				"connector":    "gpt-3_5-turbo",
			},
		},
	}
	SetStorage(mockStore)
	defer SetStorage(nil)

	// Test loading from store
	assistant, err := LoadStore("test-id")
	assert.NoError(t, err)
	assert.NotNil(t, assistant)
	assert.Equal(t, "test-id", assistant.ID)
	assert.Equal(t, "Test Assistant", assistant.Name)
	assert.Equal(t, "test-avatar", assistant.Avatar)
	assert.Equal(t, "gpt-3_5-turbo", assistant.Connector)

	// Test cache functionality
	assistant2, err := LoadStore("test-id")
	assert.NoError(t, err)
	assert.Equal(t, assistant, assistant2) // Should be the same instance from cache

	// Test non-existent assistant
	_, err = LoadStore("non-existent")
	assert.Error(t, err)
}

func TestLoad_Cache(t *testing.T) {
	prepare(t)
	defer test.Clean()

	// Clear any existing cache first
	ClearCache()

	// Test cache operations
	SetCache(2) // Set small cache size for testing
	assert.Equal(t, 2, loaded.capacity, "Cache capacity should be 2")

	// Create test assistants
	assistant1 := &Assistant{ID: "id1", Name: "Assistant 1"}
	assistant2 := &Assistant{ID: "id2", Name: "Assistant 2"}
	assistant3 := &Assistant{ID: "id3", Name: "Assistant 3"}

	// Test Put and Get
	loaded.Put(assistant1)
	assert.Equal(t, 1, loaded.Len(), "Cache should have 1 item")

	loaded.Put(assistant2)
	assert.Equal(t, 2, loaded.Len(), "Cache should have 2 items")

	// Test cache hit
	cached, exists := loaded.Get("id1")
	assert.True(t, exists)
	assert.Equal(t, assistant1, cached)

	// Test cache eviction (LRU)
	// At this point: assistant1 is most recently used (due to Get), then assistant2
	loaded.Put(assistant3) // This should evict assistant2 since it's least recently used
	assert.Equal(t, 2, loaded.Len(), "Cache should still have 2 items")
	_, exists = loaded.Get("id2")
	assert.False(t, exists, "assistant2 should have been evicted (least recently used)")
	_, exists = loaded.Get("id1")
	assert.True(t, exists, "assistant1 should still be in cache (was accessed recently)")
	_, exists = loaded.Get("id3")
	assert.True(t, exists, "assistant3 should be in cache (most recently added)")

	// Test clear cache
	ClearCache()
	assert.Nil(t, loaded)

	// Test setting new cache capacity
	SetCache(100)
	assert.NotNil(t, loaded)
}

func TestLoad_Validate(t *testing.T) {
	tests := []struct {
		name    string
		ast     *Assistant
		wantErr bool
	}{
		{
			name: "valid assistant",
			ast: &Assistant{
				ID:        "test-id",
				Name:      "Test Assistant",
				Connector: "test-connector",
			},
			wantErr: false,
		},
		{
			name: "missing id",
			ast: &Assistant{
				Name:      "Test Assistant",
				Connector: "test-connector",
			},
			wantErr: true,
		},
		{
			name: "missing name",
			ast: &Assistant{
				ID:        "test-id",
				Connector: "test-connector",
			},
			wantErr: true,
		},
		{
			name: "missing connector",
			ast: &Assistant{
				ID:   "test-id",
				Name: "Test Assistant",
			},
			wantErr: true,
		},
	}

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

func TestLoad_Clone(t *testing.T) {
	// Create a test assistant with all fields populated
	original := &Assistant{
		ID:          "test-id",
		Type:        "test-type",
		Name:        "Test Assistant",
		Avatar:      "test-avatar",
		Connector:   "test-connector",
		Path:        "test-path",
		BuiltIn:     true,
		Sort:        1,
		Description: "test description",
		Tags:        []string{"tag1", "tag2"},
		Readonly:    true,
		Mentionable: true,
		Automated:   true,
		Options:     map[string]interface{}{"key": "value"},
		Prompts:     []Prompt{{Role: "system", Content: "test"}},
		Workflow:    map[string]interface{}{"step": "test"},
	}

	// Clone the assistant
	clone := original.Clone()

	// Verify all fields are correctly cloned
	assert.Equal(t, original.ID, clone.ID)
	assert.Equal(t, original.Type, clone.Type)
	assert.Equal(t, original.Name, clone.Name)
	assert.Equal(t, original.Avatar, clone.Avatar)
	assert.Equal(t, original.Connector, clone.Connector)
	assert.Equal(t, original.Path, clone.Path)
	assert.Equal(t, original.BuiltIn, clone.BuiltIn)
	assert.Equal(t, original.Sort, clone.Sort)
	assert.Equal(t, original.Description, clone.Description)
	assert.Equal(t, original.Tags, clone.Tags)
	assert.Equal(t, original.Readonly, clone.Readonly)
	assert.Equal(t, original.Mentionable, clone.Mentionable)
	assert.Equal(t, original.Automated, clone.Automated)
	assert.Equal(t, original.Options, clone.Options)
	assert.Equal(t, original.Prompts, clone.Prompts)
	assert.Equal(t, original.Workflow, clone.Workflow)

	// Verify deep copy by modifying original
	original.Tags[0] = "modified"
	original.Options["key"] = "modified"
	original.Workflow["step"] = "modified"
	assert.NotEqual(t, original.Tags[0], clone.Tags[0])
	assert.NotEqual(t, original.Options["key"], clone.Options["key"])
	assert.NotEqual(t, original.Workflow["step"], clone.Workflow["step"])

	// Test nil case
	var nilAssistant *Assistant
	assert.Nil(t, nilAssistant.Clone())
}

func TestLoad_Update(t *testing.T) {
	// Create a test assistant
	ast := &Assistant{
		ID:        "test-id",
		Name:      "Original Name",
		Connector: "original-connector",
	}

	// Test updating various fields
	updates := map[string]interface{}{
		"name":        "Updated Name",
		"avatar":      "updated-avatar",
		"description": "Updated description",
		"connector":   "updated-connector",
		"type":        "updated-type",
		"sort":        2,
		"mentionable": true,
		"automated":   true,
		"tags":        []string{"new-tag"},
		"options":     map[string]interface{}{"new": "value"},
	}

	err := ast.Update(updates)
	assert.NoError(t, err)

	// Verify updates
	assert.Equal(t, "Updated Name", ast.Name)
	assert.Equal(t, "updated-avatar", ast.Avatar)
	assert.Equal(t, "Updated description", ast.Description)
	assert.Equal(t, "updated-connector", ast.Connector)
	assert.Equal(t, "updated-type", ast.Type)
	assert.Equal(t, 2, ast.Sort)
	assert.True(t, ast.Mentionable)
	assert.True(t, ast.Automated)
	assert.Equal(t, []string{"new-tag"}, ast.Tags)
	assert.Equal(t, map[string]interface{}{"new": "value"}, ast.Options)

	// Test nil assistant
	var nilAssistant *Assistant
	err = nilAssistant.Update(updates)
	assert.Error(t, err)

	// Test invalid update that would make the assistant invalid
	invalidUpdates := map[string]interface{}{
		"name": "",
	}
	err = ast.Update(invalidUpdates)
	assert.Error(t, err)
}

func TestLoadBuiltIn(t *testing.T) {
	prepare(t)
	defer test.Clean()

	// Clear any existing cache and storage
	ClearCache()
	SetStorage(nil)

	// Create a mock store to verify built-in assistants are saved
	mockStore := &mockStore{
		data: make(map[string]map[string]interface{}),
	}
	SetStorage(mockStore)
	SetCache(100)

	// Test loading built-in assistants
	err := LoadBuiltIn()
	assert.NoError(t, err)

	// Verify Modi assistant was loaded
	assistant, exists := loaded.Get("modi")
	assert.True(t, exists, "Modi assistant should be loaded in cache")
	if exists {
		assert.Equal(t, "modi", assistant.ID)
		assert.Equal(t, "Modi", assistant.Name)
		assert.Equal(t, "deepseek", assistant.Connector)
		assert.True(t, assistant.BuiltIn)
		assert.True(t, assistant.Readonly)
		assert.NotNil(t, assistant.Prompts)
		assert.NotNil(t, assistant.Script)
	}

}

// mockStore implements store.Store interface for testing
type mockStore struct {
	data map[string]map[string]interface{}
}

func (m *mockStore) GetAssistant(id string, locale ...string) (map[string]interface{}, error) {
	if data, ok := m.data[id]; ok {
		return data, nil
	}
	return nil, fmt.Errorf("assistant not found: %s", id)
}

// Add other required interface methods with empty implementations
func (m *mockStore) GetThread(id string) (map[string]interface{}, error)  { return nil, nil }
func (m *mockStore) GetMessage(id string) (map[string]interface{}, error) { return nil, nil }
func (m *mockStore) GetFile(id string) (map[string]interface{}, error)    { return nil, nil }
func (m *mockStore) CreateAssistant(data map[string]interface{}) (map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) CreateThread(data map[string]interface{}) (map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) CreateMessage(data map[string]interface{}) (map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) CreateFile(data map[string]interface{}) (map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) UpdateAssistant(id string, data map[string]interface{}) error { return nil }
func (m *mockStore) UpdateThread(id string, data map[string]interface{}) error    { return nil }
func (m *mockStore) UpdateMessage(id string, data map[string]interface{}) error   { return nil }
func (m *mockStore) UpdateFile(id string, data map[string]interface{}) error      { return nil }
func (m *mockStore) DeleteAssistant(id string) error                              { return nil }
func (m *mockStore) DeleteThread(id string) error                                 { return nil }
func (m *mockStore) DeleteMessage(id string) error                                { return nil }
func (m *mockStore) DeleteFile(id string) error                                   { return nil }
func (m *mockStore) ListAssistants(query map[string]interface{}) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) ListThreads(query map[string]interface{}) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) ListMessages(query map[string]interface{}) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) ListFiles(query map[string]interface{}) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) DeleteAllChats(id string) error            { return nil }
func (m *mockStore) DeleteChat(id string, chatID string) error { return nil }
func (m *mockStore) GetAssistants(filter store.AssistantFilter, locale ...string) (*store.AssistantResponse, error) {
	return nil, nil
}
func (m *mockStore) GetChat(id string, chatID string, locale ...string) (*store.ChatInfo, error) {
	return nil, nil
}
func (m *mockStore) GetChatWithFilter(id string, chatID string, filter store.ChatFilter, locale ...string) (*store.ChatInfo, error) {
	return nil, nil
}
func (m *mockStore) GetChats(id string, filter store.ChatFilter, locale ...string) (*store.ChatGroupResponse, error) {
	return nil, nil
}
func (m *mockStore) GetHistory(id string, chatID string, locale ...string) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) GetHistoryWithFilter(id string, chatID string, filter store.ChatFilter, locale ...string) ([]map[string]interface{}, error) {
	return nil, nil
}
func (m *mockStore) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
	return nil, nil
}
func (m *mockStore) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
	return nil
}
func (m *mockStore) UpdateChatTitle(sid string, cid string, title string) error   { return nil }
func (m *mockStore) DeleteAssistants(filter store.AssistantFilter) (int64, error) { return 0, nil }
func (m *mockStore) GetAssistantTags(locale ...string) ([]store.Tag, error) {
	return []store.Tag{}, nil
}

// Attachment related methods
func (m *mockStore) SaveAttachment(attachment map[string]interface{}) (interface{}, error) {
	return attachment["file_id"], nil
}

func (m *mockStore) DeleteAttachment(fileID string) error {
	return nil
}

func (m *mockStore) GetAttachments(filter store.AttachmentFilter, locale ...string) (*store.AttachmentResponse, error) {
	return &store.AttachmentResponse{}, nil
}

func (m *mockStore) GetAttachment(fileID string, locale ...string) (map[string]interface{}, error) {
	return nil, nil
}

func (m *mockStore) DeleteAttachments(filter store.AttachmentFilter) (int64, error) {
	return 0, nil
}

// Knowledge related methods
func (m *mockStore) SaveKnowledge(knowledge map[string]interface{}) (interface{}, error) {
	return knowledge["collection_id"], nil
}

func (m *mockStore) DeleteKnowledge(collectionID string) error {
	return nil
}

func (m *mockStore) GetKnowledges(filter store.KnowledgeFilter, locale ...string) (*store.KnowledgeResponse, error) {
	return &store.KnowledgeResponse{}, nil
}

func (m *mockStore) GetKnowledge(collectionID string, locale ...string) (map[string]interface{}, error) {
	return nil, nil
}

func (m *mockStore) DeleteKnowledges(filter store.KnowledgeFilter) (int64, error) {
	return 0, nil
}

// Close closes the store and releases any resources
func (m *mockStore) Close() error {
	return nil
}
