package skills

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/enmanuel/agents/pkg/skills"
	"gopkg.in/yaml.v3"
)

// Loader descubre y carga skills desde un directorio base.
type Loader struct {
	basePath string
}

// NewLoader crea un nuevo Loader apuntando al directorio de skills.
func NewLoader(basePath string) *Loader {
	return &Loader{basePath: basePath}
}

// LoadMeta carga solo la metadata (nivel 1) de todas las skills.
// Recorre el directorio base buscando SKILL.md y extrae el frontmatter YAML.
func (l *Loader) LoadMeta() ([]skills.SkillMeta, error) {
	var metas []skills.SkillMeta

	// Recorre categorias (devops/, analysis/, etc.)
	categories, err := os.ReadDir(l.basePath)
	if err != nil {
		return nil, fmt.Errorf("read skills dir: %w", err)
	}

	for _, catEntry := range categories {
		if !catEntry.IsDir() {
			continue
		}

		category := catEntry.Name()
		catPath := filepath.Join(l.basePath, category)

		// Recorre skills dentro de la categoria
		skillDirs, err := os.ReadDir(catPath)
		if err != nil {
			continue
		}

		for _, skillEntry := range skillDirs {
			if !skillEntry.IsDir() {
				continue
			}

			skillName := skillEntry.Name()
			skillPath := filepath.Join(catPath, skillName)
			skillMdPath := filepath.Join(skillPath, "SKILL.md")

			// Verificar que existe SKILL.md
			if _, err := os.Stat(skillMdPath); os.IsNotExist(err) {
				continue
			}

			// Parsear metadata
			meta, _, err := parseSkillMD(skillMdPath)
			if err != nil {
				continue // skip invalid skills
			}

			meta.Category = category
			metas = append(metas, meta)
		}
	}

	return metas, nil
}

// LoadSkill carga una skill completa (nivel 2) por nombre.
// Retorna el struct Skill con metadata, instrucciones y listado de recursos.
func (l *Loader) LoadSkill(name string) (*skills.Skill, error) {
	// Buscar en todas las categorias
	categories, err := os.ReadDir(l.basePath)
	if err != nil {
		return nil, fmt.Errorf("read skills dir: %w", err)
	}

	for _, catEntry := range categories {
		if !catEntry.IsDir() {
			continue
		}

		category := catEntry.Name()
		skillPath := filepath.Join(l.basePath, category, name)
		skillMdPath := filepath.Join(skillPath, "SKILL.md")

		if _, err := os.Stat(skillMdPath); os.IsNotExist(err) {
			continue
		}

		// Parsear skill completa
		meta, instructions, err := parseSkillMD(skillMdPath)
		if err != nil {
			return nil, fmt.Errorf("parse %s: %w", skillMdPath, err)
		}

		meta.Category = category

		skill := &skills.Skill{
			Meta:         meta,
			Instructions: instructions,
			BasePath:     skillPath,
			Scripts:      listFiles(filepath.Join(skillPath, "scripts")),
			References:   listFiles(filepath.Join(skillPath, "references")),
			Templates:    listFiles(filepath.Join(skillPath, "templates")),
			Assets:       listFiles(filepath.Join(skillPath, "assets")),
		}

		return skill, nil
	}

	return nil, fmt.Errorf("skill not found: %s", name)
}

// ReadResource lee un recurso especifico (nivel 3) de una skill.
// path es relativo a la skill (ej: "scripts/deploy.sh", "references/api.md").
func (l *Loader) ReadResource(skillName, resourcePath string) (string, error) {
	skill, err := l.LoadSkill(skillName)
	if err != nil {
		return "", err
	}

	fullPath := filepath.Join(skill.BasePath, resourcePath)

	// Validar que el path esta dentro de la skill (evitar path traversal)
	absBasePath, err := filepath.Abs(skill.BasePath)
	if err != nil {
		return "", fmt.Errorf("abs base path: %w", err)
	}

	absFullPath, err := filepath.Abs(fullPath)
	if err != nil {
		return "", fmt.Errorf("abs resource path: %w", err)
	}

	if !strings.HasPrefix(absFullPath, absBasePath) {
		return "", fmt.Errorf("path traversal detected: %s", resourcePath)
	}

	content, err := os.ReadFile(absFullPath)
	if err != nil {
		return "", fmt.Errorf("read resource: %w", err)
	}

	return string(content), nil
}

// parseSkillMD extrae el frontmatter YAML y el cuerpo markdown de un SKILL.md.
func parseSkillMD(path string) (skills.SkillMeta, string, error) {
	f, err := os.Open(path)
	if err != nil {
		return skills.SkillMeta{}, "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	var yamlLines []string
	var bodyLines []string
	inYAML := false
	yamlClosed := false

	for scanner.Scan() {
		line := scanner.Text()

		if strings.TrimSpace(line) == "---" {
			if !inYAML {
				inYAML = true
				continue
			} else {
				inYAML = false
				yamlClosed = true
				continue
			}
		}

		if inYAML {
			yamlLines = append(yamlLines, line)
		} else if yamlClosed {
			bodyLines = append(bodyLines, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return skills.SkillMeta{}, "", err
	}

	// Parse YAML frontmatter
	var meta skills.SkillMeta
	yamlStr := strings.Join(yamlLines, "\n")
	if err := yaml.Unmarshal([]byte(yamlStr), &meta); err != nil {
		return skills.SkillMeta{}, "", fmt.Errorf("parse yaml: %w", err)
	}

	// Cuerpo markdown
	body := strings.Join(bodyLines, "\n")

	return meta, body, nil
}

// listFiles retorna una lista de archivos (rutas relativas) dentro de un directorio.
// Si el directorio no existe, retorna una lista vacia.
func listFiles(dir string) []string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil
	}

	var files []string
	for _, entry := range entries {
		if !entry.IsDir() {
			files = append(files, entry.Name())
		}
	}
	return files
}
