package installmeta

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	toml "github.com/pelletier/go-toml"
)

const (
	DefaultDir                  = "/etc/banger"
	DefaultPath                 = DefaultDir + "/install.toml"
	DefaultService              = "bangerd.service"
	DefaultRootHelperService    = "bangerd-root.service"
	DefaultSocketPath           = "/run/banger/bangerd.sock"
	DefaultRootHelperRuntimeDir = "/run/banger-root"
	DefaultRootHelperSocketPath = DefaultRootHelperRuntimeDir + "/bangerd-root.sock"
)

type Metadata struct {
	OwnerUser   string    `toml:"owner_user"`
	OwnerUID    int       `toml:"owner_uid"`
	OwnerGID    int       `toml:"owner_gid"`
	OwnerHome   string    `toml:"owner_home"`
	InstalledAt time.Time `toml:"installed_at"`
	Version     string    `toml:"version,omitempty"`
	Commit      string    `toml:"commit,omitempty"`
	BuiltAt     string    `toml:"built_at,omitempty"`
}

func LookupOwner(name string) (Metadata, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return Metadata{}, fmt.Errorf("owner username is required")
	}
	entry, err := user.Lookup(name)
	if err != nil {
		return Metadata{}, err
	}
	uid, err := strconv.Atoi(entry.Uid)
	if err != nil {
		return Metadata{}, fmt.Errorf("parse owner uid %q: %w", entry.Uid, err)
	}
	gid, err := strconv.Atoi(entry.Gid)
	if err != nil {
		return Metadata{}, fmt.Errorf("parse owner gid %q: %w", entry.Gid, err)
	}
	home := strings.TrimSpace(entry.HomeDir)
	if home == "" || !filepath.IsAbs(home) {
		return Metadata{}, fmt.Errorf("owner %q has invalid home directory %q", name, entry.HomeDir)
	}
	return Metadata{
		OwnerUser: name,
		OwnerUID:  uid,
		OwnerGID:  gid,
		OwnerHome: home,
	}, nil
}

func Load(path string) (Metadata, error) {
	if strings.TrimSpace(path) == "" {
		path = DefaultPath
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return Metadata{}, err
	}
	var meta Metadata
	if err := toml.Unmarshal(data, &meta); err != nil {
		return Metadata{}, err
	}
	if err := meta.Validate(); err != nil {
		return Metadata{}, err
	}
	return meta, nil
}

func Save(path string, meta Metadata) error {
	if strings.TrimSpace(path) == "" {
		path = DefaultPath
	}
	if err := meta.Validate(); err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}
	data, err := toml.Marshal(meta)
	if err != nil {
		return err
	}
	return os.WriteFile(path, data, 0o644)
}

// UpdateBuildInfo refreshes only the Version / Commit / BuiltAt
// fields on the install metadata, preserving everything else
// (OwnerUser/UID/GID/Home and the original InstalledAt timestamp).
// Used by `banger update` to record what's running after a
// successful binary swap; the install identity is unchanged so
// re-running `banger system install` is not required.
//
// Errors when path doesn't exist or can't be parsed — `banger
// update` runs in system mode where install.toml IS the source of
// truth; a missing file means we shouldn't be updating at all.
func UpdateBuildInfo(path, version, commit, builtAt string) error {
	if strings.TrimSpace(path) == "" {
		path = DefaultPath
	}
	meta, err := Load(path)
	if err != nil {
		return err
	}
	meta.Version = strings.TrimSpace(version)
	meta.Commit = strings.TrimSpace(commit)
	meta.BuiltAt = strings.TrimSpace(builtAt)
	return Save(path, meta)
}

func (m Metadata) Validate() error {
	if strings.TrimSpace(m.OwnerUser) == "" {
		return fmt.Errorf("install metadata missing owner_user")
	}
	if m.OwnerUID < 0 {
		return fmt.Errorf("install metadata has invalid owner_uid %d", m.OwnerUID)
	}
	if m.OwnerGID < 0 {
		return fmt.Errorf("install metadata has invalid owner_gid %d", m.OwnerGID)
	}
	if strings.TrimSpace(m.OwnerHome) == "" || !filepath.IsAbs(m.OwnerHome) {
		return fmt.Errorf("install metadata has invalid owner_home %q", m.OwnerHome)
	}
	return nil
}
