package registries

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

	"github.com/spf13/afero"
)

const (
	OfficialName = "official"
	officialURL  = "https://github.com/AvengeMedia/dms-plugin-registry.git"
)

// Source identifies a registry repository. Name doubles as the per-registry
// cache subdirectory, so it is restricted to a filesystem-safe slug.
type Source struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

func (s Source) Official() bool {
	return s.Name == OfficialName
}

var nameRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,31}$`)

func configPath() (string, error) {
	configDir, err := os.UserConfigDir()
	if err != nil {
		return "", fmt.Errorf("failed to get user config dir: %w", err)
	}
	return filepath.Join(configDir, "DankMaterialShell", "registries.json"), nil
}

// Load returns the official registry followed by any user-configured extras.
// A missing or unreadable config yields just the official registry.
func Load(fs afero.Fs) []Source {
	sources := []Source{{Name: OfficialName, URL: officialURL}}

	path, err := configPath()
	if err != nil {
		return sources
	}
	data, err := afero.ReadFile(fs, path)
	if err != nil {
		return sources
	}
	var extras []Source
	if err := json.Unmarshal(data, &extras); err != nil {
		return sources
	}
	for _, s := range extras {
		if !nameRe.MatchString(s.Name) || s.Name == OfficialName || s.URL == "" {
			continue
		}
		sources = append(sources, s)
	}
	return sources
}

func saveExtras(fs afero.Fs, extras []Source) error {
	path, err := configPath()
	if err != nil {
		return err
	}
	if err := fs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return fmt.Errorf("failed to create config dir: %w", err)
	}
	data, err := json.MarshalIndent(extras, "", "  ")
	if err != nil {
		return err
	}
	return afero.WriteFile(fs, path, append(data, '\n'), 0o644)
}

func loadExtras(fs afero.Fs) []Source {
	sources := Load(fs)
	return sources[1:]
}

func Add(fs afero.Fs, name, url string) error {
	name = strings.TrimSpace(name)
	url = strings.TrimSpace(url)

	if !nameRe.MatchString(name) {
		return fmt.Errorf("invalid registry name %q: use 1-32 lowercase letters, digits or hyphens", name)
	}
	if name == OfficialName {
		return fmt.Errorf("registry name %q is reserved", OfficialName)
	}
	if url == "" {
		return fmt.Errorf("registry URL is required")
	}

	extras := loadExtras(fs)
	for _, s := range extras {
		if s.Name == name {
			return fmt.Errorf("registry %q already exists", name)
		}
		if s.URL == url {
			return fmt.Errorf("registry %q already uses this URL", s.Name)
		}
	}

	return saveExtras(fs, append(extras, Source{Name: name, URL: url}))
}

func Remove(fs afero.Fs, name string) error {
	if name == OfficialName {
		return fmt.Errorf("the official registry cannot be removed")
	}

	extras := loadExtras(fs)
	kept := make([]Source, 0, len(extras))
	for _, s := range extras {
		if s.Name != name {
			kept = append(kept, s)
		}
	}
	if len(kept) == len(extras) {
		return fmt.Errorf("registry %q not found", name)
	}

	return saveExtras(fs, kept)
}
