package plugins

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"

	"github.com/muyue/muyue/internal/agent"
)

type PluginStatus string

const (
	StatusEnabled  PluginStatus = "enabled"
	StatusDisabled PluginStatus = "disabled"
	StatusError    PluginStatus = "error"
)

type Plugin struct {
	name        string
	version     string
	description string
	status      PluginStatus
	tools       []*agent.ToolDefinition
	hooks       map[HookType]HookFunc
	init        func(ctx context.Context, registry *agent.Registry) error
}

func NewPlugin(name, version, description string) *Plugin {
	return &Plugin{
		name:        name,
		version:     version,
		description: description,
		status:      StatusDisabled,
		tools:       make([]*agent.ToolDefinition, 0),
		hooks:       make(map[HookType]HookFunc),
	}
}

func (p *Plugin) Name() string        { return p.name }
func (p *Plugin) Version() string     { return p.version }
func (p *Plugin) Description() string { return p.description }
func (p *Plugin) Status() PluginStatus { return p.status }

func (p *Plugin) AddTool(tool *ToolDefinition) *Plugin {
	td := &agent.ToolDefinition{
		Name:        tool.Name,
		Description: tool.Description,
		Params:      tool.Params,
		Handler:     tool.Handler,
	}
	p.tools = append(p.tools, td)
	return p
}

func (p *Plugin) AddToolGeneric(params interface{}, name, description string, handler func(ctx context.Context, raw json.RawMessage) (agent.ToolResponse, error)) *Plugin {
	paramsSchema, err := generatePluginSchema(params)
	if err == nil {
		td := &agent.ToolDefinition{
			Name:        name,
			Description: description,
			Params:      paramsSchema,
			Handler:     handler,
		}
		p.tools = append(p.tools, td)
	}
	return p
}

func (p *Plugin) AddHook(hookType HookType, fn HookFunc) *Plugin {
	p.hooks[hookType] = fn
	return p
}

func (p *Plugin) SetInit(fn func(ctx context.Context, registry *agent.Registry) error) *Plugin {
	p.init = fn
	return p
}

type ToolDefinition struct {
	Name        string
	Description string
	Params      json.RawMessage
	Handler     func(ctx context.Context, args json.RawMessage) (agent.ToolResponse, error)
}

type PluginInfo struct {
	Name        string       `json:"name"`
	Version     string       `json:"version"`
	Description string       `json:"description"`
	Status      PluginStatus `json:"status"`
	ToolCount   int          `json:"tool_count"`
	HookTypes   []string     `json:"hook_types,omitempty"`
	Error       string       `json:"error,omitempty"`
}

type Manager struct {
	mu      sync.RWMutex
	plugins map[string]*Plugin
	hooks   *HookRegistry
	enabled map[string]bool
}

func NewManager(hooks *HookRegistry) *Manager {
	return &Manager{
		plugins: make(map[string]*Plugin),
		hooks:   hooks,
		enabled: make(map[string]bool),
	}
}

func (m *Manager) Register(p *Plugin) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.plugins[p.name]; exists {
		return fmt.Errorf("plugin %q already registered", p.name)
	}

	m.plugins[p.name] = p
	return nil
}

func (m *Manager) Enable(ctx context.Context, name string, registry *agent.Registry) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	p, ok := m.plugins[name]
	if !ok {
		return fmt.Errorf("plugin %q not found", name)
	}

	if p.status == StatusEnabled {
		return nil
	}

	if p.init != nil {
		if err := p.init(ctx, registry); err != nil {
			p.status = StatusError
			return fmt.Errorf("plugin %q init failed: %w", name, err)
		}
	}

	for _, tool := range p.tools {
		if err := registry.Register(tool); err != nil {
			p.status = StatusError
			return fmt.Errorf("plugin %q register tool %q: %w", name, tool.Name, err)
		}
	}

	for hookType, fn := range p.hooks {
		m.hooks.Register(hookType, name, 10, fn)
	}

	p.status = StatusEnabled
	m.enabled[name] = true
	return nil
}

func (m *Manager) Disable(name string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	p, ok := m.plugins[name]
	if !ok {
		return
	}

	if p.status != StatusEnabled {
		return
	}

	m.hooks.RemoveByPlugin(name)
	p.status = StatusDisabled
	delete(m.enabled, name)
}

func (m *Manager) Get(name string) (*Plugin, bool) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	p, ok := m.plugins[name]
	return p, ok
}

func (m *Manager) List() []PluginInfo {
	m.mu.RLock()
	defer m.mu.RUnlock()

	result := make([]PluginInfo, 0, len(m.plugins))
	for _, p := range m.plugins {
		info := PluginInfo{
			Name:        p.name,
			Version:     p.version,
			Description: p.description,
			Status:      p.status,
			ToolCount:   len(p.tools),
		}
		for ht := range p.hooks {
			info.HookTypes = append(info.HookTypes, string(ht))
		}
		result = append(result, info)
	}
	return result
}

func (m *Manager) EnabledNames() []string {
	m.mu.RLock()
	defer m.mu.RUnlock()

	names := make([]string, 0, len(m.enabled))
	for name := range m.enabled {
		names = append(names, name)
	}
	return names
}

func (m *Manager) EnableFromConfig(ctx context.Context, enabledList []string, registry *agent.Registry) {
	for _, name := range enabledList {
		if err := m.Enable(ctx, name, registry); err != nil {
			_ = err
		}
	}
}
