package skills

import (
	"fmt"
	"sync"

	"github.com/ClareAI/astra-insight-mcp/internal/gateway"
	"github.com/ClareAI/astra-insight-mcp/internal/mcp"
)

// RegisterFunc registers a group of tools on the MCP server.
type RegisterFunc func(s *mcp.Server, gw *gateway.Client)

// Skill is a named, lazily-activated group of related MCP tools.
type Skill struct {
	Name        string
	Description string
	ToolNames   []string     // populated after first activation
	Context     string       // expert knowledge injected when activated (optional)
	register    RegisterFunc // registers all tools in this skill
}

// Registry manages skill definitions and their activation state.
type Registry struct {
	mu     sync.Mutex
	skills []*Skill
	active map[string]bool
	srv    *mcp.Server
	gw     *gateway.Client
}

// NewRegistry creates a skill registry bound to a server and gateway client.
func NewRegistry(srv *mcp.Server, gw *gateway.Client) *Registry {
	return &Registry{
		skills: make([]*Skill, 0),
		active: make(map[string]bool),
		srv:    srv,
		gw:     gw,
	}
}

// Define adds a skill definition. Tools are not registered until Activate is called.
func (r *Registry) Define(name, description string, toolNames []string, fn RegisterFunc) {
	r.skills = append(r.skills, &Skill{
		Name:        name,
		Description: description,
		ToolNames:   toolNames,
		register:    fn,
	})
}

// DefineWithContext is like Define but also attaches expert knowledge that
// gets returned to the LLM when the skill is activated.
func (r *Registry) DefineWithContext(name, description string, toolNames []string, fn RegisterFunc, context string) {
	r.skills = append(r.skills, &Skill{
		Name:        name,
		Description: description,
		ToolNames:   toolNames,
		Context:     context,
		register:    fn,
	})
}

// Activate registers a skill's tools on the MCP server.
// Returns the skill's expert context (may be empty).
func (r *Registry) Activate(name string) (string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.active[name] {
		return "", fmt.Errorf("skill %q is already active", name)
	}

	sk := r.find(name)
	if sk == nil {
		return "", fmt.Errorf("unknown skill: %s", name)
	}

	sk.register(r.srv, r.gw)
	r.active[name] = true
	return sk.Context, nil
}

// Deactivate removes a skill's tools from the MCP server.
func (r *Registry) Deactivate(name string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if !r.active[name] {
		return fmt.Errorf("skill %q is not active", name)
	}

	sk := r.find(name)
	if sk == nil {
		return fmt.Errorf("unknown skill: %s", name)
	}

	r.srv.UnregisterTools(sk.ToolNames)
	delete(r.active, name)
	return nil
}

// IsActive returns whether a skill is currently activated.
func (r *Registry) IsActive(name string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.active[name]
}

// List returns a snapshot of all skills with their activation state.
func (r *Registry) List() []SkillInfo {
	r.mu.Lock()
	defer r.mu.Unlock()
	out := make([]SkillInfo, len(r.skills))
	for i, sk := range r.skills {
		out[i] = SkillInfo{
			Name:        sk.Name,
			Description: sk.Description,
			ToolCount:   len(sk.ToolNames),
			Tools:       sk.ToolNames,
			Active:      r.active[sk.Name],
			HasContext:  sk.Context != "",
		}
	}
	return out
}

// SkillInfo is the public view of a skill.
type SkillInfo struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	ToolCount   int      `json:"tool_count"`
	Tools       []string `json:"tools"`
	Active      bool     `json:"active"`
	HasContext  bool     `json:"has_context,omitempty"`
}

// DeactivateUnused deactivates any active skill whose tools were not in the
// provided set. Returns the names of deactivated skills. Useful for cleaning
// up skills that were activated but never used during a conversation turn.
func (r *Registry) DeactivateUnused(usedTools map[string]bool) []string {
	r.mu.Lock()
	defer r.mu.Unlock()

	var deactivated []string
	for _, sk := range r.skills {
		if !r.active[sk.Name] || len(sk.ToolNames) == 0 {
			continue
		}
		used := false
		for _, tn := range sk.ToolNames {
			if usedTools[tn] {
				used = true
				break
			}
		}
		if !used {
			r.srv.UnregisterTools(sk.ToolNames)
			delete(r.active, sk.Name)
			deactivated = append(deactivated, sk.Name)
		}
	}
	return deactivated
}

// ActivateIfInactive silently activates a skill if it is not already active.
// Unlike Activate, it does not return an error for already-active skills.
func (r *Registry) ActivateIfInactive(name string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.active[name] {
		return
	}
	sk := r.find(name)
	if sk == nil {
		return
	}
	sk.register(r.srv, r.gw)
	r.active[name] = true
}

func (r *Registry) find(name string) *Skill {
	for _, sk := range r.skills {
		if sk.Name == name {
			return sk
		}
	}
	return nil
}
