package infra

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

	"gopkg.in/yaml.v3"
)

// VaultManifestEntry is a single vault entry parsed from a projects/<proj>/vaults/vault.yaml.
type VaultManifestEntry struct {
	ProjectID    string   // basename of projects/<proj>/, inferred from manifest path
	Name         string   // vault name as declared in vault.yaml
	Description  string   // human description
	Path         string   // absolute path to the vault directory
	Tags         []string // tags declared in vault.yaml
	ManifestFile string   // absolute path to the vault.yaml this entry came from
}

// vaultYAML mirrors the vault.yaml schema (only the fields we care about).
type vaultYAML struct {
	Vaults []struct {
		Name        string   `yaml:"name"`
		Description string   `yaml:"description"`
		Path        string   `yaml:"path"`
		Tags        []string `yaml:"tags"`
	} `yaml:"vaults"`
}

// VaultManifestRead globs all projects/*/vaults/vault.yaml under repoRoot, parses each
// manifest and returns a flat slice of VaultManifestEntry.
//
// Rules:
//   - If a manifest fails to parse, an error is returned immediately with the file path.
//   - If no manifests are found, an empty slice is returned (not an error).
//   - ProjectID is inferred from the directory component between "projects/" and "/vaults/".
func VaultManifestRead(repoRoot string) ([]VaultManifestEntry, error) {
	pattern := filepath.Join(repoRoot, "projects", "*", "vaults", "vault.yaml")
	matches, err := filepath.Glob(pattern)
	if err != nil {
		return nil, fmt.Errorf("vault_manifest_read: glob %q: %w", pattern, err)
	}

	var out []VaultManifestEntry
	for _, manifestPath := range matches {
		entries, err := parseVaultManifest(manifestPath)
		if err != nil {
			return nil, err
		}
		out = append(out, entries...)
	}
	return out, nil
}

func parseVaultManifest(manifestPath string) ([]VaultManifestEntry, error) {
	data, err := os.ReadFile(manifestPath)
	if err != nil {
		return nil, fmt.Errorf("vault_manifest_read: read %q: %w", manifestPath, err)
	}

	var raw vaultYAML
	if err := yaml.Unmarshal(data, &raw); err != nil {
		return nil, fmt.Errorf("vault_manifest_read: parse %q: %w", manifestPath, err)
	}

	projectID := inferProjectID(manifestPath)

	entries := make([]VaultManifestEntry, 0, len(raw.Vaults))
	for _, v := range raw.Vaults {
		entries = append(entries, VaultManifestEntry{
			ProjectID:    projectID,
			Name:         v.Name,
			Description:  v.Description,
			Path:         v.Path,
			Tags:         v.Tags,
			ManifestFile: manifestPath,
		})
	}
	return entries, nil
}

// inferProjectID extracts the project basename from a path of the form
// .../projects/<proj>/vaults/vault.yaml.
func inferProjectID(manifestPath string) string {
	// Normalize separators and split.
	parts := strings.Split(filepath.ToSlash(manifestPath), "/")
	// Walk backwards: vault.yaml -> vaults -> <proj> -> projects -> ...
	for i, p := range parts {
		if p == "projects" && i+1 < len(parts) {
			return parts[i+1]
		}
	}
	return ""
}
