package plugins

import (
	"fmt"
	"testing"
	"time"

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

// MockPlugin is a test plugin implementation
type MockPlugin struct {
	name        string
	version     string
	description string
	shouldFail  bool
	shouldWait  time.Duration
}

func (mp *MockPlugin) Name() string {
	return mp.name
}

func (mp *MockPlugin) Version() string {
	return mp.version
}

func (mp *MockPlugin) Description() string {
	return mp.description
}

func (mp *MockPlugin) Execute(input map[string]interface{}) (map[string]interface{}, error) {
	if mp.shouldWait > 0 {
		time.Sleep(mp.shouldWait)
	}

	if mp.shouldFail {
		return nil, fmt.Errorf("plugin execution failed")
	}

	return map[string]interface{}{
		"result": "success",
		"input":  input,
	}, nil
}

func (mp *MockPlugin) Validate() error {
	if mp.name == "" {
		return fmt.Errorf("plugin name cannot be empty")
	}
	return nil
}

func TestNewPluginRegistry(t *testing.T) {
	registry := NewPluginRegistry()
	assert.NotNil(t, registry)
	assert.Equal(t, 0, registry.stats.TotalPlugins)
}

func TestRegisterPlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:        "test-plugin",
		version:     "1.0.0",
		description: "Test plugin",
	}

	err := registry.Register(plugin, "test-author", nil)
	assert.NoError(t, err)
	assert.Equal(t, 1, registry.stats.TotalPlugins)
}

func TestRegisterPluginNil(t *testing.T) {
	registry := NewPluginRegistry()

	err := registry.Register(nil, "test-author", nil)
	assert.Error(t, err)
}

func TestRegisterDuplicatePlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:    "test-plugin",
		version: "1.0.0",
	}

	registry.Register(plugin, "author", nil)
	err := registry.Register(plugin, "author", nil)
	assert.Error(t, err)
}

func TestUnregisterPlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)
	assert.Equal(t, 1, registry.stats.TotalPlugins)

	err := registry.Unregister("test-plugin")
	assert.NoError(t, err)
	assert.Equal(t, 0, registry.stats.TotalPlugins)
}

func TestExecutePlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	input := map[string]interface{}{"key": "value"}
	output, err := registry.Execute("test-plugin", input)

	assert.NoError(t, err)
	assert.NotNil(t, output)
	assert.Equal(t, "success", output["result"])
}

func TestExecutePluginNotFound(t *testing.T) {
	registry := NewPluginRegistry()

	_, err := registry.Execute("nonexistent", map[string]interface{}{})
	assert.Error(t, err)
}

func TestExecutePluginDisabled(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	registry.DisablePlugin("test-plugin")

	_, err := registry.Execute("test-plugin", map[string]interface{}{})
	assert.Error(t, err)
}

func TestExecutePluginFailure(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:       "test-plugin",
		version:    "1.0.0",
		shouldFail: true,
	}
	registry.Register(plugin, "author", nil)

	_, err := registry.Execute("test-plugin", map[string]interface{}{})
	assert.Error(t, err)
	assert.Equal(t, 1, registry.stats.FailedExecutions)
}

func TestGetPlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	retrieved, found := registry.Get("test-plugin")
	assert.True(t, found)
	assert.Equal(t, "test-plugin", retrieved.Name())
}

func TestGetPluginNotFound(t *testing.T) {
	registry := NewPluginRegistry()

	_, found := registry.Get("nonexistent")
	assert.False(t, found)
}

func TestGetMetadata(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:        "test-plugin",
		version:     "1.0.0",
		description: "Test description",
	}
	registry.Register(plugin, "test-author", nil)

	meta, found := registry.GetMetadata("test-plugin")
	assert.True(t, found)
	assert.Equal(t, "test-plugin", meta.Name)
	assert.Equal(t, "1.0.0", meta.Version)
	assert.Equal(t, "test-author", meta.Author)
	assert.Equal(t, "plugin://test-plugin", meta.URL)
	assert.True(t, meta.Enabled)
}

func TestListPlugins(t *testing.T) {
	registry := NewPluginRegistry()

	for i := 0; i < 3; i++ {
		plugin := &MockPlugin{
			name:    fmt.Sprintf("plugin-%d", i),
			version: "1.0.0",
		}
		registry.Register(plugin, "author", nil)
	}

	plugins := registry.ListPlugins()
	assert.Equal(t, 3, len(plugins))
}

func TestEnableDisablePlugin(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	assert.Equal(t, 1, registry.stats.EnabledPlugins)

	registry.DisablePlugin("test-plugin")
	assert.Equal(t, 0, registry.stats.EnabledPlugins)

	registry.EnablePlugin("test-plugin")
	assert.Equal(t, 1, registry.stats.EnabledPlugins)
}

func TestGetExecutionLog(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	registry.Execute("test-plugin", map[string]interface{}{})
	registry.Execute("test-plugin", map[string]interface{}{})

	log := registry.GetExecutionLog("test-plugin")
	assert.Equal(t, 2, len(log))
}

func TestExecutionLogSuccess(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	registry.Execute("test-plugin", map[string]interface{}{})

	log := registry.GetExecutionLog("test-plugin")
	assert.Equal(t, 1, len(log))
	assert.True(t, log[0].Success)
	assert.Nil(t, log[0].Error)
}

func TestExecutionLogFailure(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:       "test-plugin",
		version:    "1.0.0",
		shouldFail: true,
	}
	registry.Register(plugin, "author", nil)

	registry.Execute("test-plugin", map[string]interface{}{})

	log := registry.GetExecutionLog("test-plugin")
	assert.Equal(t, 1, len(log))
	assert.False(t, log[0].Success)
	assert.NotNil(t, log[0].Error)
}

func TestGetStats(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	registry.Execute("test-plugin", map[string]interface{}{})
	registry.Execute("test-plugin", map[string]interface{}{})

	stats := registry.GetStats()
	assert.Equal(t, 1, stats.TotalPlugins)
	assert.Equal(t, 2, stats.TotalExecutions)
	assert.Equal(t, 2, stats.SuccessfulExecutions)
}

func TestResolvePluginURL(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	resolved, err := registry.ResolvePluginURL("plugin://test-plugin")
	assert.NoError(t, err)
	assert.Equal(t, "test-plugin", resolved.Name())
}

func TestResolvePluginURLInvalid(t *testing.T) {
	registry := NewPluginRegistry()

	_, err := registry.ResolvePluginURL("http://example.com")
	assert.Error(t, err)
}

func TestResolvePluginURLNotFound(t *testing.T) {
	registry := NewPluginRegistry()

	_, err := registry.ResolvePluginURL("plugin://nonexistent")
	assert.Error(t, err)
}

func TestClear(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	assert.Equal(t, 1, registry.stats.TotalPlugins)

	registry.Clear()
	assert.Equal(t, 0, registry.stats.TotalPlugins)
}

func TestIsPluginURL(t *testing.T) {
	assert.True(t, IsPluginURL("plugin://test"))
	assert.False(t, IsPluginURL("http://test"))
	assert.False(t, IsPluginURL("file://test"))
}

func TestExtractPluginName(t *testing.T) {
	name := ExtractPluginName("plugin://test-plugin")
	assert.Equal(t, "test-plugin", name)

	name = ExtractPluginName("http://test")
	assert.Equal(t, "", name)
}

func TestExecutionTiming(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{
		name:        "test-plugin",
		version:     "1.0.0",
		shouldWait:  50 * time.Millisecond,
	}
	registry.Register(plugin, "author", nil)

	registry.Execute("test-plugin", map[string]interface{}{})

	log := registry.GetExecutionLog("test-plugin")
	assert.Greater(t, log[0].Duration, 40*time.Millisecond)
}

func TestMultiplePlugins(t *testing.T) {
	registry := NewPluginRegistry()

	for i := 0; i < 5; i++ {
		plugin := &MockPlugin{
			name:    fmt.Sprintf("plugin-%d", i),
			version: "1.0.0",
		}
		registry.Register(plugin, "author", nil)
	}

	assert.Equal(t, 5, registry.stats.TotalPlugins)

	for i := 0; i < 5; i++ {
		registry.Execute(fmt.Sprintf("plugin-%d", i), map[string]interface{}{})
	}

	stats := registry.GetStats()
	assert.Equal(t, 5, stats.TotalExecutions)
}

func TestPluginConfig(t *testing.T) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	config := map[string]interface{}{
		"setting1": "value1",
		"setting2": 42,
	}

	registry.Register(plugin, "author", config)

	meta, _ := registry.GetMetadata("test-plugin")
	assert.NotNil(t, meta.Config)
	assert.Equal(t, "value1", meta.Config["setting1"])
	assert.Equal(t, 42, meta.Config["setting2"])
}

func BenchmarkRegisterPlugin(b *testing.B) {
	registry := NewPluginRegistry()

	for i := 0; i < b.N; i++ {
		plugin := &MockPlugin{
			name:    fmt.Sprintf("plugin-%d", i),
			version: "1.0.0",
		}
		registry.Register(plugin, "author", nil)
	}
}

func BenchmarkExecutePlugin(b *testing.B) {
	registry := NewPluginRegistry()

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	registry.Register(plugin, "author", nil)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		registry.Execute("test-plugin", map[string]interface{}{})
	}
}
