package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

// rawFunction mirrors the YAML frontmatter of a function .md file.
type rawFunction struct {
	Name            string    `yaml:"name"`
	Kind            string    `yaml:"kind"`
	Lang            string    `yaml:"lang"`
	Domain          string    `yaml:"domain"`
	Version         string    `yaml:"version"`
	Purity          string    `yaml:"purity"`
	Signature       string    `yaml:"signature"`
	Description     string    `yaml:"description"`
	Tags            []string  `yaml:"tags"`
	UsesFunctions   []string  `yaml:"uses_functions"`
	UsesTypes       []string  `yaml:"uses_types"`
	Returns         []string  `yaml:"returns"`
	ReturnsOptional bool      `yaml:"returns_optional"`
	ErrorType       string    `yaml:"error_type"`
	Imports         []string  `yaml:"imports"`
	Tested          bool      `yaml:"tested"`
	Tests           []string  `yaml:"tests"`
	TestFilePath    string    `yaml:"test_file_path"`
	FilePath        string    `yaml:"file_path"`

	// Params schema
	Params []rawParam `yaml:"params"`
	Output string     `yaml:"output"`

	// Source attribution
	SourceRepo    string `yaml:"source_repo"`
	SourceLicense string `yaml:"source_license"`
	SourceFile    string `yaml:"source_file"`

	// Component fields
	Props     []PropDef `yaml:"props"`
	Emits     []string  `yaml:"emits"`
	HasState  *bool     `yaml:"has_state"`
	Framework string    `yaml:"framework"`
	Variant   []string  `yaml:"variant"`
}

// rawParam describes a function parameter with semantic meaning.
type rawParam struct {
	Name string `yaml:"name" json:"name"`
	Desc string `yaml:"desc" json:"desc"`
}

// rawType mirrors the YAML frontmatter of a type .md file.
type rawType struct {
	Name        string   `yaml:"name"`
	Lang        string   `yaml:"lang"`
	Domain      string   `yaml:"domain"`
	Version     string   `yaml:"version"`
	Algebraic   string   `yaml:"algebraic"`
	Definition  string   `yaml:"definition"`
	Description string   `yaml:"description"`
	Tags        []string `yaml:"tags"`
	UsesTypes     []string `yaml:"uses_types"`
	FilePath      string   `yaml:"file_path"`
	SourceRepo    string   `yaml:"source_repo"`
	SourceLicense string   `yaml:"source_license"`
	SourceFile    string   `yaml:"source_file"`
}

// rawApp mirrors the YAML frontmatter of an app .md file.
type rawApp struct {
	Name          string      `yaml:"name"`
	Lang          string      `yaml:"lang"`
	Domain        string      `yaml:"domain"`
	Version       string      `yaml:"version"`
	Description   string      `yaml:"description"`
	Tags          []string    `yaml:"tags"`
	UsesFunctions []string    `yaml:"uses_functions"`
	UsesTypes     []string    `yaml:"uses_types"`
	UsesModules   []string    `yaml:"uses_modules"`
	Framework     string      `yaml:"framework"`
	EntryPoint    string      `yaml:"entry_point"`
	DirPath       string      `yaml:"dir_path"`
	RepoURL       string      `yaml:"repo_url"`
	Service       *rawService `yaml:"service"`
}

// rawService mirrors the `service:` block of an app.md frontmatter (issue 0105).
type rawService struct {
	Port           *int     `yaml:"port"`
	HealthEndpoint string   `yaml:"health_endpoint"`
	HealthTimeoutS int      `yaml:"health_timeout_s"`
	SystemdUnit    string   `yaml:"systemd_unit"`
	SystemdScope   string   `yaml:"systemd_scope"`
	RestartPolicy  string   `yaml:"restart_policy"`
	Runtime        string   `yaml:"runtime"`
	IsLocalOnly    bool     `yaml:"is_local_only"`
	PCTargets      []string `yaml:"pc_targets"`
}

// rawAnalysis mirrors the YAML frontmatter of an analysis .md file.
type rawAnalysis struct {
	Name          string   `yaml:"name"`
	Lang          string   `yaml:"lang"`
	Domain        string   `yaml:"domain"`
	Description   string   `yaml:"description"`
	Tags          []string `yaml:"tags"`
	UsesFunctions []string `yaml:"uses_functions"`
	UsesTypes     []string `yaml:"uses_types"`
	UsesModules   []string `yaml:"uses_modules"`
	Framework     string   `yaml:"framework"`
	EntryPoint    string   `yaml:"entry_point"`
	DirPath       string   `yaml:"dir_path"`
	RepoURL       string   `yaml:"repo_url"`
}

// rawModule mirrors the YAML frontmatter of a module.md file.
type rawModule struct {
	Name        string   `yaml:"name"`
	Version     string   `yaml:"version"`
	Lang        string   `yaml:"lang"`
	Description string   `yaml:"description"`
	Members     []string `yaml:"members"`
	Tags        []string `yaml:"tags"`
	DirPath     string   `yaml:"dir_path"`
	RepoURL     string   `yaml:"repo_url"`
}

// rawProject mirrors the YAML frontmatter of a project .md file.
type rawProject struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description"`
	Tags        []string `yaml:"tags"`
	RepoURL     string   `yaml:"repo_url"`
}

// rawVaultFile mirrors the YAML of a vault.yaml manifest file.
type rawVaultFile struct {
	Vaults []rawVaultEntry `yaml:"vaults"`
}

// rawVaultEntry describes a single vault in vault.yaml.
type rawVaultEntry struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description"`
	Path        string   `yaml:"path"`
	Tags        []string `yaml:"tags"`
}

// extractFrontmatter splits a .md file into YAML frontmatter and body.
func extractFrontmatter(data []byte) ([]byte, []byte, error) {
	content := data
	if !bytes.HasPrefix(content, []byte("---\n")) && !bytes.HasPrefix(content, []byte("---\r\n")) {
		return nil, nil, fmt.Errorf("missing opening --- in frontmatter")
	}

	// Skip opening ---
	rest := content[4:]
	idx := bytes.Index(rest, []byte("\n---"))
	if idx < 0 {
		return nil, nil, fmt.Errorf("missing closing --- in frontmatter")
	}

	fm := rest[:idx]
	body := rest[idx+4:] // skip \n---
	return fm, body, nil
}

// ParseFunctionMD parses a function .md file into a Function.
// root is the registry root directory, used to resolve file_path for code reading.
func ParseFunctionMD(path string, root string) (*Function, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawFunction
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Kind == "" {
		return nil, fmt.Errorf("%s: kind is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}

	sections := extractSections(body)

	f := &Function{
		ID:              GenerateID(raw.Name, raw.Lang, raw.Domain),
		Name:            raw.Name,
		Kind:            Kind(raw.Kind),
		Lang:            raw.Lang,
		Domain:          raw.Domain,
		Version:         raw.Version,
		Purity:          Purity(raw.Purity),
		Signature:       raw.Signature,
		Description:     raw.Description,
		Tags:            raw.Tags,
		UsesFunctions:   raw.UsesFunctions,
		UsesTypes:       raw.UsesTypes,
		Returns:         raw.Returns,
		ReturnsOptional: raw.ReturnsOptional,
		ErrorType:       raw.ErrorType,
		Imports:         raw.Imports,
		Example:         sections.example,
		Notes:           sections.notes,
		Documentation:   sections.documentation,
		Tested:          raw.Tested,
		Tests:           raw.Tests,
		TestFilePath:    raw.TestFilePath,
		FilePath:        raw.FilePath,
		Props:           raw.Props,
		Emits:           raw.Emits,
		HasState:        raw.HasState,
		Framework:       raw.Framework,
		Variant:         raw.Variant,
		SourceRepo:      raw.SourceRepo,
		SourceLicense:   raw.SourceLicense,
		SourceFile:      raw.SourceFile,
	}

	// Serialize params + output to JSON for params_schema column
	if len(raw.Params) > 0 || raw.Output != "" {
		schema := struct {
			Params []rawParam `json:"params,omitempty"`
			Output string     `json:"output,omitempty"`
		}{Params: raw.Params, Output: raw.Output}
		if b, err := json.Marshal(schema); err == nil {
			f.ParamsSchema = string(b)
		}
	}

	if root != "" && raw.FilePath != "" {
		codePath := filepath.Join(root, raw.FilePath)
		if codeData, err := os.ReadFile(codePath); err == nil {
			f.Code = string(codeData)
		}
	}

	return f, nil
}

// ParseTypeMD parses a type .md file into a Type.
// root is the registry root directory, used to resolve file_path for code reading.
func ParseTypeMD(path string, root string) (*Type, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawType
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}

	sections := extractSections(body)

	t := &Type{
		ID:            GenerateID(raw.Name, raw.Lang, raw.Domain),
		Name:          raw.Name,
		Lang:          raw.Lang,
		Domain:        raw.Domain,
		Version:       raw.Version,
		Algebraic:     Algebraic(raw.Algebraic),
		Definition:    strings.TrimSpace(raw.Definition),
		Description:   raw.Description,
		Tags:          raw.Tags,
		UsesTypes:     raw.UsesTypes,
		SourceRepo:    raw.SourceRepo,
		SourceLicense: raw.SourceLicense,
		SourceFile:    raw.SourceFile,
		Examples:      sections.example,
		Notes:         sections.notes,
		Documentation: sections.documentation,
		FilePath:      raw.FilePath,
	}

	if root != "" && raw.FilePath != "" {
		codePath := filepath.Join(root, raw.FilePath)
		if codeData, err := os.ReadFile(codePath); err == nil {
			t.Code = string(codeData)
		}
	}

	return t, nil
}

// ParseAppMD parses an app .md file into an App.
func ParseAppMD(path string, root string) (*App, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawApp
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}

	sections := extractSections(body)

	if raw.Version == "" {
		raw.Version = "0.1.0"
	}

	a := &App{
		ID:            GenerateID(raw.Name, raw.Lang, raw.Domain),
		Name:          raw.Name,
		Lang:          raw.Lang,
		Domain:        raw.Domain,
		Version:       raw.Version,
		Description:   raw.Description,
		Tags:          raw.Tags,
		UsesFunctions: raw.UsesFunctions,
		UsesTypes:     raw.UsesTypes,
		UsesModules:   raw.UsesModules,
		Framework:     raw.Framework,
		EntryPoint:    raw.EntryPoint,
		Documentation: sections.documentation,
		Notes:         sections.notes,
		DirPath:       raw.DirPath,
		RepoURL:       raw.RepoURL,
	}

	if raw.Service != nil {
		spec := &ServiceSpec{
			HealthEndpoint: raw.Service.HealthEndpoint,
			HealthTimeoutS: raw.Service.HealthTimeoutS,
			SystemdUnit:    raw.Service.SystemdUnit,
			SystemdScope:   raw.Service.SystemdScope,
			RestartPolicy:  raw.Service.RestartPolicy,
			Runtime:        raw.Service.Runtime,
			IsLocalOnly:    raw.Service.IsLocalOnly,
			PCTargets:      raw.Service.PCTargets,
		}
		if raw.Service.Port != nil {
			spec.Port = *raw.Service.Port
		}
		a.Service = spec
	}

	return a, nil
}

// ParseAnalysisMD parses an analysis .md file into an Analysis.
func ParseAnalysisMD(path string, root string) (*Analysis, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawAnalysis
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}

	sections := extractSections(body)

	an := &Analysis{
		ID:            GenerateID(raw.Name, raw.Lang, raw.Domain),
		Name:          raw.Name,
		Lang:          raw.Lang,
		Domain:        raw.Domain,
		Description:   raw.Description,
		Tags:          raw.Tags,
		UsesFunctions: raw.UsesFunctions,
		UsesTypes:     raw.UsesTypes,
		UsesModules:   raw.UsesModules,
		Framework:     raw.Framework,
		EntryPoint:    raw.EntryPoint,
		Documentation: sections.documentation,
		Notes:         sections.notes,
		DirPath:       raw.DirPath,
		RepoURL:       raw.RepoURL,
	}

	return an, nil
}

// ParseModuleMD parses a module .md file into a Module.
func ParseModuleMD(path string, root string) (*Module, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawModule
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Lang == "" {
		return nil, fmt.Errorf("%s: lang is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}
	if raw.Version == "" {
		raw.Version = "0.0.0"
	}

	sections := extractSections(body)

	m := &Module{
		ID:            GenerateModuleID(raw.Name, raw.Lang),
		Name:          raw.Name,
		Version:       raw.Version,
		Lang:          raw.Lang,
		Description:   raw.Description,
		Members:       raw.Members,
		Tags:          raw.Tags,
		DirPath:       raw.DirPath,
		RepoURL:       raw.RepoURL,
		Documentation: sections.documentation,
		Notes:         sections.notes,
	}

	return m, nil
}

// ParseProjectMD parses a project .md file into a Project.
func ParseProjectMD(path string, root string) (*Project, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	fm, body, err := extractFrontmatter(data)
	if err != nil {
		return nil, fmt.Errorf("parsing %s: %w", path, err)
	}

	var raw rawProject
	if err := yaml.Unmarshal(fm, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	if raw.Name == "" {
		return nil, fmt.Errorf("%s: name is required", path)
	}
	if raw.Description == "" {
		return nil, fmt.Errorf("%s: description is required", path)
	}

	sections := extractSections(body)

	p := &Project{
		ID:            raw.Name,
		Name:          raw.Name,
		Description:   raw.Description,
		Tags:          raw.Tags,
		RepoURL:       raw.RepoURL,
		DirPath:       filepath.Join("projects", raw.Name),
		Documentation: sections.documentation,
		Notes:         sections.notes,
	}

	return p, nil
}

// ParseVaultYAML parses a vault.yaml manifest into a slice of Vaults.
// projectID is the owning project ID, or "" for registry-level vaults.
// vaultsDir is the directory containing vault.yaml (used to detect symlinks).
func ParseVaultYAML(path string, projectID string, vaultsDir string) ([]*Vault, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %w", path, err)
	}

	var raw rawVaultFile
	if err := yaml.Unmarshal(data, &raw); err != nil {
		return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
	}

	var vaults []*Vault
	for _, rv := range raw.Vaults {
		if rv.Name == "" {
			continue
		}

		suffix := projectID
		if suffix == "" {
			suffix = "registry"
		}
		id := rv.Name + "_" + suffix

		// Detect if the vault entry on disk is a symlink
		isSymlink := false
		vaultPath := rv.Path
		entryPath := filepath.Join(vaultsDir, rv.Name)
		if fi, err := os.Lstat(entryPath); err == nil {
			if fi.Mode()&os.ModeSymlink != 0 {
				isSymlink = true
				if target, err := os.Readlink(entryPath); err == nil && vaultPath == "" {
					vaultPath = target
				}
			}
		}

		vaults = append(vaults, &Vault{
			ID:          id,
			Name:        rv.Name,
			ProjectID:   projectID,
			Description: rv.Description,
			Path:        vaultPath,
			Symlink:     isSymlink,
			Tags:        rv.Tags,
		})
	}

	return vaults, nil
}

// bodySections holds the extracted sections from a .md body.
type bodySections struct {
	example       string // content under ## Ejemplo
	notes         string // content under ## Notas
	documentation string // everything else
}

// extractSections splits the markdown body into named sections.
// Known sections (## Ejemplo, ## Notas) are extracted separately.
// All other content (including unknown ## headings) goes into documentation.
func extractSections(body []byte) bodySections {
	lines := strings.Split(string(body), "\n")
	var s bodySections

	type section struct {
		name  string
		lines []string
	}

	var current *section
	var sections []section

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(trimmed, "## ") {
			if current != nil {
				sections = append(sections, *current)
			}
			current = &section{name: trimmed}
			continue
		}
		if current != nil {
			current.lines = append(current.lines, line)
		} else {
			// Content before any ## heading goes to documentation
			sections = append(sections, section{name: "_preamble", lines: []string{line}})
		}
	}
	if current != nil {
		sections = append(sections, *current)
	}

	var docParts []string
	for _, sec := range sections {
		content := strings.TrimSpace(strings.Join(sec.lines, "\n"))
		if content == "" && sec.name == "_preamble" {
			continue
		}
		switch {
		case strings.HasPrefix(sec.name, "## Ejemplo"):
			s.example = content
		case strings.HasPrefix(sec.name, "## Notas"):
			s.notes = content
		case sec.name == "_preamble":
			docParts = append(docParts, content)
		default:
			// Unknown sections go to documentation with their heading
			docParts = append(docParts, sec.name+"\n\n"+content)
		}
	}
	s.documentation = strings.TrimSpace(strings.Join(docParts, "\n\n"))

	return s
}
