package main

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"os"
	"regexp"

	"gopkg.in/yaml.v3"
)

// Registration mirrors a Synapse application-service registration.yaml. Like the
// mautrix bridges, the bot GENERATES this file (random tokens) and then READS its
// own tokens back from it — so the same file is the single source of truth shared
// with Synapse, and tokens are never hand-copied into two places.
type Registration struct {
	ID              string        `yaml:"id"`
	URL             string        `yaml:"url"`
	ASToken         string        `yaml:"as_token"`
	HSToken         string        `yaml:"hs_token"`
	SenderLocalpart string        `yaml:"sender_localpart"`
	RateLimited     bool          `yaml:"rate_limited"`
	Namespaces      RegNamespaces `yaml:"namespaces"`
}

type RegNamespaces struct {
	Users   []RegNamespace `yaml:"users"`
	Aliases []RegNamespace `yaml:"aliases"`
	Rooms   []RegNamespace `yaml:"rooms"`
}

type RegNamespace struct {
	Exclusive bool   `yaml:"exclusive"`
	Regex     string `yaml:"regex"`
}

func randToken() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

// LoadRegistration reads and validates a registration.yaml.
func LoadRegistration(path string) (*Registration, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var reg Registration
	if err := yaml.Unmarshal(data, &reg); err != nil {
		return nil, fmt.Errorf("parse registration %s: %w", path, err)
	}
	if reg.ASToken == "" || reg.HSToken == "" {
		return nil, fmt.Errorf("registration %s missing as_token/hs_token", path)
	}
	return &reg, nil
}

// GenerateRegistration writes a fresh registration.yaml with random tokens. It
// REFUSES to overwrite an existing file — regenerating would rotate the tokens
// and break the running Synapse binding; delete the file to intentionally regen.
func GenerateRegistration(path, asURL, localpart, serverName string) error {
	if _, err := os.Stat(path); err == nil {
		return fmt.Errorf("registration already exists at %s (delete it to regenerate — that rotates tokens and breaks the live Synapse binding)", path)
	}
	asTok, err := randToken()
	if err != nil {
		return err
	}
	hsTok, err := randToken()
	if err != nil {
		return err
	}
	reg := &Registration{
		ID:              "ai-bot",
		URL:             asURL,
		ASToken:         asTok,
		HSToken:         hsTok,
		SenderLocalpart: localpart,
		RateLimited:     false,
		Namespaces: RegNamespaces{
			Users:   []RegNamespace{{Exclusive: true, Regex: "@" + regexp.QuoteMeta(localpart+":"+serverName)}},
			Aliases: []RegNamespace{},
			Rooms:   []RegNamespace{},
		},
	}
	body, err := yaml.Marshal(reg)
	if err != nil {
		return err
	}
	header := "# Generated by `ai-bot generate-registration`. Mount this file into the\n" +
		"# Synapse container and add it to app_service_config_files, then restart\n" +
		"# Synapse. The bot reads its tokens from THIS file via REGISTRATION_PATH —\n" +
		"# do not hand-copy the tokens elsewhere.\n"
	// 0644, not 0600: this file is shared with the Synapse container, which runs
	// as a DIFFERENT uid (non-root) and must be able to read it — same as the
	// mautrix bridge registrations. (Token secrecy relies on host access control,
	// not file mode, on the single-tenant VPS.)
	return os.WriteFile(path, append([]byte(header), body...), 0o644)
}
