package plugins

import (
	"os"
	"path/filepath"
	"testing"

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

func TestNewPluginLoader(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	assert.NotNil(t, loader)
	assert.Equal(t, registry, loader.registry)
}

func TestRegisterLoadedPlugin(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	plugin := &MockPlugin{name: "test-plugin", version: "1.0.0"}
	err := loader.RegisterLoadedPlugin(plugin, "test-author", nil)

	assert.NoError(t, err)
	assert.True(t, loader.IsPluginLoaded("test-plugin"))
}

func TestUnloadPlugin(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

	assert.True(t, loader.IsPluginLoaded("test-plugin"))

	err := loader.UnloadPlugin("test-plugin")
	assert.NoError(t, err)
	assert.False(t, loader.IsPluginLoaded("test-plugin"))
}

func TestUnloadPluginNotFound(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	err := loader.UnloadPlugin("nonexistent")
	assert.Error(t, err)
}

func TestReloadPlugin(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

	_, _ = loader.GetLoadTime("test-plugin")

	err := loader.ReloadPlugin("test-plugin")
	assert.NoError(t, err)

	loadedPlugins := loader.GetLoadedPlugins()
	assert.Equal(t, 1, loadedPlugins["test-plugin"].ReloadCount)
}

func TestReloadPluginNotFound(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	err := loader.ReloadPlugin("nonexistent")
	assert.Error(t, err)
}

func TestGetLoadedPlugins(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	for i := 0; i < 3; i++ {
		plugin := &MockPlugin{
			name:    string(rune(48 + i)) + "-plugin",
			version: "1.0.0",
		}
		loader.RegisterLoadedPlugin(plugin, "author", nil)
	}

	loaded := loader.GetLoadedPlugins()
	assert.Equal(t, 3, len(loaded))
}

func TestGetLoadTime(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

	loadTime, exists := loader.GetLoadTime("test-plugin")
	assert.True(t, exists)
	assert.NotZero(t, loadTime)
}

func TestIsPluginLoaded(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	assert.False(t, loader.IsPluginLoaded("test-plugin"))

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

	assert.True(t, loader.IsPluginLoaded("test-plugin"))
}

func TestLoaderExecutePlugin(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

	output, err := loader.ExecutePlugin("test-plugin", map[string]interface{}{})
	assert.NoError(t, err)
	assert.NotNil(t, output)
}

func TestLoaderExecutePluginNotLoaded(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

func TestGetPluginStats(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

	stats, err := loader.GetPluginStats("test-plugin")
	assert.NoError(t, err)
	assert.NotNil(t, stats)
}

func TestGetPluginStatsNotLoaded(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	_, err := loader.GetPluginStats("nonexistent")
	assert.Error(t, err)
}

func TestClose(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	for i := 0; i < 3; i++ {
		plugin := &MockPlugin{
			name:    string(rune(48+i)) + "-plugin",
			version: "1.0.0",
		}
		loader.RegisterLoadedPlugin(plugin, "author", nil)
	}

	assert.Equal(t, 3, len(loader.GetLoadedPlugins()))

	err := loader.Close()
	assert.NoError(t, err)
	assert.Equal(t, 0, len(loader.GetLoadedPlugins()))
}

func TestGetPluginRegistry(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	retrieved := loader.GetPluginRegistry()
	assert.Equal(t, registry, retrieved)
}

func TestLoadPlugin(t *testing.T) {
	tmpDir := t.TempDir()
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, tmpDir)

	// For now, test with plugin:// URL (no file loading needed)
	err := loader.LoadPlugin("plugin://test-plugin")
	assert.NoError(t, err)
}

func TestLoadPluginDirectory(t *testing.T) {
	tmpDir := t.TempDir()
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, tmpDir)

	// Create some mock config files
	configContent := `{
		"name": "test-plugin",
		"version": "1.0.0",
		"author": "test-author",
		"path": "test-plugin"
	}`

	configFile := filepath.Join(tmpDir, "test-plugin.json")
	os.WriteFile(configFile, []byte(configContent), 0644)

	// Load from directory (won't actually load plugins without more setup)
	err := loader.LoadPluginDirectory(tmpDir)
	assert.NoError(t, err)
}

func TestLoadPluginDirectoryNotFound(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	err := loader.LoadPluginDirectory("/nonexistent/directory")
	assert.Error(t, err)
}

func TestGetFailedLoads(t *testing.T) {
	tmpDir := t.TempDir()
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, tmpDir)

	// Try to load from nonexistent file
	loader.LoadPlugin(filepath.Join(tmpDir, "nonexistent.json"))

	failed := loader.GetFailedLoads()
	assert.Greater(t, len(failed), 0)
}

func TestMultiplePluginLifecycle(t *testing.T) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	// Load plugins
	for i := 0; i < 5; i++ {
		plugin := &MockPlugin{
			name:    string(rune(48+i)) + "-plugin",
			version: "1.0.0",
		}
		err := loader.RegisterLoadedPlugin(plugin, "author", nil)
		assert.NoError(t, err)
	}

	assert.Equal(t, 5, len(loader.GetLoadedPlugins()))

	// Execute plugins
	for i := 0; i < 5; i++ {
		name := string(rune(48+i)) + "-plugin"
		output, err := loader.ExecutePlugin(name, map[string]interface{}{})
		assert.NoError(t, err)
		assert.NotNil(t, output)
	}

	// Unload plugins
	for i := 0; i < 5; i++ {
		name := string(rune(48+i)) + "-plugin"
		err := loader.UnloadPlugin(name)
		assert.NoError(t, err)
	}

	assert.Equal(t, 0, len(loader.GetLoadedPlugins()))
}

func BenchmarkRegisterLoadedPlugin(b *testing.B) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

	for i := 0; i < b.N; i++ {
		plugin := &MockPlugin{
			name:    string(rune(48+i%100)) + "-plugin",
			version: "1.0.0",
		}
		loader.RegisterLoadedPlugin(plugin, "author", nil)
	}
}

func BenchmarkExecuteLoadedPlugin(b *testing.B) {
	registry := NewPluginRegistry()
	loader := NewPluginLoader(registry, "/tmp/plugins")

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

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