package stacks

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

	"gopkg.in/yaml.v3"
)

// Metadata holds app information parsed from .felhom.yml.
type Metadata struct {
	DisplayName    string                `yaml:"display_name" json:"display_name"`
	Description    string                `yaml:"description" json:"description"`
	Category       string                `yaml:"category" json:"category"`
	Subdomain      string                `yaml:"subdomain" json:"subdomain"`
	Slug           string                `yaml:"slug" json:"slug"`
	Resources      ResourceHints         `yaml:"resources" json:"resources"`
	DeployFields   []DeployField         `yaml:"deploy_fields" json:"deploy_fields"`
	AppInfo        AppInfo               `yaml:"app_info" json:"app_info"`
	OptionalConfig []OptionalConfigGroup `yaml:"optional_config" json:"optional_config"`
}

// AppInfo holds detailed app information for the info page.
type AppInfo struct {
	Tagline       string   `yaml:"tagline" json:"tagline"`
	UseCases      []string `yaml:"use_cases" json:"use_cases"`
	FirstSteps    []string `yaml:"first_steps" json:"first_steps"`
	Prerequisites []string `yaml:"prerequisites" json:"prerequisites"`
	DefaultCreds  string   `yaml:"default_creds" json:"default_creds"`
	DocsURL       string   `yaml:"docs_url" json:"docs_url"`
}

// OptionalConfigGroup defines a group of optional config fields (e.g., "Metadata providers").
type OptionalConfigGroup struct {
	Group       string                `yaml:"group" json:"group"`
	Description string                `yaml:"description" json:"description"`
	Fields      []OptionalConfigField `yaml:"fields" json:"fields"`
}

// OptionalConfigField defines an individual optional config field.
type OptionalConfigField struct {
	EnvVar   string `yaml:"env_var" json:"env_var"`
	Label    string `yaml:"label" json:"label"`
	Type     string `yaml:"type" json:"type"`
	HelpURL  string `yaml:"help_url" json:"help_url"`
	HelpText string `yaml:"help_text" json:"help_text"`
}

// ResourceHints describe what the app needs.
type ResourceHints struct {
	MemRequest    string `yaml:"mem_request" json:"mem_request"`
	MemLimit      string `yaml:"mem_limit" json:"mem_limit"`
	PiCompatible  bool   `yaml:"pi_compatible" json:"pi_compatible"`
	NeedsHDD      bool   `yaml:"needs_hdd" json:"needs_hdd"`
}

// DeployField defines one configuration field shown during first deployment.
type DeployField struct {
	EnvVar           string          `yaml:"env_var" json:"env_var"`
	Label            string          `yaml:"label" json:"label"`
	Type             string          `yaml:"type" json:"type"`               // domain, secret, password, path, text, select, boolean
	Generate         string          `yaml:"generate" json:"generate"`       // e.g., "password:24", "hex:32", "static:admin"
	Default          string          `yaml:"default" json:"default"`
	Required         bool            `yaml:"required" json:"required"`
	Placeholder      string          `yaml:"placeholder" json:"placeholder"`
	Description      string          `yaml:"description" json:"description"`
	LockedAfterDeploy bool           `yaml:"locked_after_deploy" json:"locked_after_deploy"`
	Options          []SelectOption  `yaml:"options" json:"options,omitempty"`
}

// SelectOption is a choice for "select" type fields.
type SelectOption struct {
	Value string `yaml:"value" json:"value"`
	Label string `yaml:"label" json:"label"`
}

// LoadMetadata reads .felhom.yml from a stack directory.
// Returns default metadata if the file doesn't exist.
func LoadMetadata(stackDir string) Metadata {
	meta := Metadata{}

	path := filepath.Join(stackDir, ".felhom.yml")
	data, err := os.ReadFile(path)
	if err != nil {
		// No metadata file — build defaults from directory name
		dirName := filepath.Base(stackDir)
		meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " "))
		meta.Slug = dirName
		meta.Category = "tools"
		return meta
	}

	if err := yaml.Unmarshal(data, &meta); err != nil {
		fmt.Fprintf(os.Stderr, "[ERROR] Failed to parse .felhom.yml in %s: %v\n", stackDir, err)
		dirName := filepath.Base(stackDir)
		meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " "))
		meta.Slug = dirName
		return meta
	}

	// Fill in defaults for missing fields
	dirName := filepath.Base(stackDir)
	if meta.Slug == "" {
		meta.Slug = dirName
	}
	if meta.DisplayName == "" {
		meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " "))
	}
	if meta.Category == "" {
		meta.Category = "tools"
	}

	// DOMAIN field is always auto-filled — mark it implicitly required
	for i := range meta.DeployFields {
		if meta.DeployFields[i].Type == "domain" {
			meta.DeployFields[i].Required = true
			meta.DeployFields[i].LockedAfterDeploy = true
		}
		// secret fields are always locked after deploy
		if meta.DeployFields[i].Type == "secret" {
			meta.DeployFields[i].LockedAfterDeploy = true
		}
	}

	return meta
}

// HasDeployFields returns true if the app has any user-facing deploy fields
// (i.e., fields beyond auto-filled domain and auto-generated secrets).
func (m *Metadata) HasDeployFields() bool {
	for _, f := range m.DeployFields {
		if f.Type != "domain" && f.Type != "secret" {
			return true
		}
	}
	return false
}

// UserFacingFields returns only fields the user needs to interact with.
// Excludes auto-filled (domain) and fully hidden (secret) fields.
func (m *Metadata) UserFacingFields() []DeployField {
	var fields []DeployField
	for _, f := range m.DeployFields {
		if f.Type != "domain" && f.Type != "secret" {
			fields = append(fields, f)
		}
	}
	return fields
}

// AutoGeneratedFields returns fields that are generated without user input.
func (m *Metadata) AutoGeneratedFields() []DeployField {
	var fields []DeployField
	for _, f := range m.DeployFields {
		if f.Type == "secret" || f.Type == "domain" {
			fields = append(fields, f)
		}
	}
	return fields
}

// HasAppInfo returns true if the metadata has any app info content.
func (m *Metadata) HasAppInfo() bool {
	return m.AppInfo.Tagline != "" || len(m.AppInfo.UseCases) > 0 || len(m.AppInfo.FirstSteps) > 0
}

// HasOptionalConfig returns true if the metadata has any optional config groups.
func (m *Metadata) HasOptionalConfig() bool {
	return len(m.OptionalConfig) > 0
}
