package plugins

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// PluginLoader loads and manages plugin lifecycle
type PluginLoader struct {
	mu              sync.RWMutex
	registry        *PluginRegistry
	pluginPath      string
	loadedPlugins   map[string]*LoadedPlugin
	loadTime        map[string]time.Time
	failedLoads     map[string]error
}

// LoadedPlugin represents a loaded plugin with additional metadata
type LoadedPlugin struct {
	Plugin      SkillPlugin
	LoadedAt    time.Time
	ReloadCount int
	LastError   error
}

// PluginConfig represents plugin configuration from file
type PluginConfig struct {
	Name        string                 `json:"name"`
	Version     string                 `json:"version"`
	Author      string                 `json:"author"`
	Description string                 `json:"description"`
	Path        string                 `json:"path"`
	Config      map[string]interface{} `json:"config,omitempty"`
	Enabled     bool                   `json:"enabled"`
}

// NewPluginLoader creates a new plugin loader
func NewPluginLoader(registry *PluginRegistry, pluginPath string) *PluginLoader {
	return &PluginLoader{
		registry:      registry,
		pluginPath:    pluginPath,
		loadedPlugins: make(map[string]*LoadedPlugin),
		loadTime:      make(map[string]time.Time),
		failedLoads:   make(map[string]error),
	}
}

// LoadPlugin loads a plugin from URL
func (pl *PluginLoader) LoadPlugin(url string) error {
	pl.mu.Lock()
	defer pl.mu.Unlock()

	if IsPluginURL(url) {
		// Already loaded - no need to load from file
		return nil
	}

	// Try to load from file
	return pl.loadFromFileLocked(url)
}

// loadFromFileLocked loads a plugin from a file (must be called with lock held)
func (pl *PluginLoader) loadFromFileLocked(path string) error {
	// Read config file
	data, err := os.ReadFile(path)
	if err != nil {
		pl.failedLoads[path] = err
		return fmt.Errorf("failed to read plugin config: %w", err)
	}

	var config PluginConfig
	if err := json.Unmarshal(data, &config); err != nil {
		pl.failedLoads[path] = err
		return fmt.Errorf("failed to parse plugin config: %w", err)
	}

	// For now, return a placeholder plugin load
	// In a real implementation, this would use reflection or plugin packages
	// to dynamically load compiled plugins
	pl.loadTime[config.Name] = time.Now()

	return nil
}

// LoadPluginDirectory loads all plugins from a directory
func (pl *PluginLoader) LoadPluginDirectory(directory string) error {
	entries, err := os.ReadDir(directory)
	if err != nil {
		return fmt.Errorf("failed to read plugin directory: %w", err)
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		if filepath.Ext(entry.Name()) == ".json" {
			pluginPath := filepath.Join(directory, entry.Name())
			if err := pl.LoadPlugin(pluginPath); err != nil {
				// Log error but continue loading other plugins
				pl.mu.Lock()
				pl.failedLoads[pluginPath] = err
				pl.mu.Unlock()
			}
		}
	}

	return nil
}

// RegisterLoadedPlugin registers a loaded plugin with the registry
func (pl *PluginLoader) RegisterLoadedPlugin(plugin SkillPlugin, author string, config map[string]interface{}) error {
	pl.mu.Lock()
	defer pl.mu.Unlock()

	if err := pl.registry.Register(plugin, author, config); err != nil {
		pl.failedLoads[plugin.Name()] = err
		return err
	}

	now := time.Now()
	pl.loadedPlugins[plugin.Name()] = &LoadedPlugin{
		Plugin:    plugin,
		LoadedAt:  now,
		LastError: nil,
	}
	pl.loadTime[plugin.Name()] = now

	return nil
}

// UnloadPlugin unloads a plugin
func (pl *PluginLoader) UnloadPlugin(name string) error {
	pl.mu.Lock()
	defer pl.mu.Unlock()

	if _, exists := pl.loadedPlugins[name]; !exists {
		return fmt.Errorf("plugin not loaded: %s", name)
	}

	err := pl.registry.Unregister(name)
	if err == nil {
		delete(pl.loadedPlugins, name)
		delete(pl.loadTime, name)
	}

	return err
}

// ReloadPlugin reloads a plugin
func (pl *PluginLoader) ReloadPlugin(name string) error {
	pl.mu.Lock()
	defer pl.mu.Unlock()

	loadedPlugin, exists := pl.loadedPlugins[name]
	if !exists {
		return fmt.Errorf("plugin not loaded: %s", name)
	}

	// Re-validate plugin
	if err := loadedPlugin.Plugin.Validate(); err != nil {
		pl.failedLoads[name] = err
		loadedPlugin.LastError = err
		return fmt.Errorf("plugin validation failed: %w", err)
	}

	loadedPlugin.ReloadCount++
	loadedPlugin.LastError = nil
	pl.loadTime[name] = time.Now()

	return nil
}

// GetLoadedPlugins returns all loaded plugins
func (pl *PluginLoader) GetLoadedPlugins() map[string]*LoadedPlugin {
	pl.mu.RLock()
	defer pl.mu.RUnlock()

	result := make(map[string]*LoadedPlugin)
	for name, plugin := range pl.loadedPlugins {
		result[name] = plugin
	}

	return result
}

// GetFailedLoads returns all failed plugin loads
func (pl *PluginLoader) GetFailedLoads() map[string]error {
	pl.mu.RLock()
	defer pl.mu.RUnlock()

	result := make(map[string]error)
	for path, err := range pl.failedLoads {
		result[path] = err
	}

	return result
}

// GetLoadTime returns when a plugin was loaded
func (pl *PluginLoader) GetLoadTime(name string) (time.Time, bool) {
	pl.mu.RLock()
	defer pl.mu.RUnlock()

	t, exists := pl.loadTime[name]
	return t, exists
}

// IsPluginLoaded checks if a plugin is loaded
func (pl *PluginLoader) IsPluginLoaded(name string) bool {
	pl.mu.RLock()
	defer pl.mu.RUnlock()

	_, exists := pl.loadedPlugins[name]
	return exists
}

// ExecutePlugin executes a loaded plugin
func (pl *PluginLoader) ExecutePlugin(name string, input map[string]interface{}) (map[string]interface{}, error) {
	pl.mu.RLock()
	if _, exists := pl.loadedPlugins[name]; !exists {
		pl.mu.RUnlock()
		return nil, fmt.Errorf("plugin not loaded: %s", name)
	}
	pl.mu.RUnlock()

	return pl.registry.Execute(name, input)
}

// GetPluginStats returns stats for a loaded plugin
func (pl *PluginLoader) GetPluginStats(name string) (*PluginStats, error) {
	pl.mu.RLock()
	defer pl.mu.RUnlock()

	if _, exists := pl.loadedPlugins[name]; !exists {
		return nil, fmt.Errorf("plugin not loaded: %s", name)
	}

	return pl.registry.GetStats(), nil
}

// Close closes the plugin loader and unloads all plugins
func (pl *PluginLoader) Close() error {
	pl.mu.Lock()
	defer pl.mu.Unlock()

	var lastErr error
	for name := range pl.loadedPlugins {
		if err := pl.registry.Unregister(name); err != nil {
			lastErr = err
		}
	}

	pl.loadedPlugins = make(map[string]*LoadedPlugin)
	pl.loadTime = make(map[string]time.Time)

	return lastErr
}

// GetPluginRegistry returns the underlying registry
func (pl *PluginLoader) GetPluginRegistry() *PluginRegistry {
	return pl.registry
}
