// Command adminctl manages admin-backend accounts from the CLI.
//
// The first administrator must be created here (the web UI has no self-signup):
//
//	adminctl create -username alice
//
// Requires DB_DSN and ADMIN_SECRET_KEY in the environment (same as the server).
// On success it prints the otpauth:// provisioning URI and Base32 secret to the
// terminal so the operator can add it to an authenticator app / render a QR.
package main

import (
	"bufio"
	"context"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"

	"golang.org/x/term"

	"github.com/wangjia/pangolin/server/internal/admin"
	"github.com/wangjia/pangolin/server/internal/db"
	"github.com/wangjia/pangolin/server/internal/totp"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(2)
	}
	switch os.Args[1] {
	case "create":
		if err := runCreate(os.Args[2:]); err != nil {
			fmt.Fprintln(os.Stderr, "error:", err)
			os.Exit(1)
		}
	case "-h", "--help", "help":
		usage()
	default:
		usage()
		os.Exit(2)
	}
}

func usage() {
	fmt.Fprintln(os.Stdout, "adminctl — Pangolin admin account tool")
	fmt.Fprintln(os.Stdout, "")
	fmt.Fprintln(os.Stdout, "Usage:")
	fmt.Fprintln(os.Stdout, "  adminctl create -username <name>")
	fmt.Fprintln(os.Stdout, "")
	fmt.Fprintln(os.Stdout, "Env: DB_DSN, ADMIN_SECRET_KEY (32-byte hex/base64)")
}

func runCreate(args []string) error {
	var username string
	fs := flag.NewFlagSet("create", flag.ContinueOnError)
	fs.StringVar(&username, "username", "", "admin username")
	if err := fs.Parse(args); err != nil {
		return err
	}
	username = strings.TrimSpace(username)
	if username == "" {
		return errors.New("-username is required")
	}

	dsn := os.Getenv("DB_DSN")
	if dsn == "" {
		return errors.New("DB_DSN is required")
	}
	keyStr := os.Getenv("ADMIN_SECRET_KEY")
	if keyStr == "" {
		return errors.New("ADMIN_SECRET_KEY is required")
	}
	key, err := parseSecretKey(keyStr)
	if err != nil {
		return err
	}

	password, err := readPassword()
	if err != nil {
		return err
	}
	if len(password) < 8 {
		return errors.New("password must be at least 8 characters")
	}

	pwHash, err := admin.HashPassword(password)
	if err != nil {
		return err
	}
	secret, err := totp.GenerateSecret()
	if err != nil {
		return err
	}
	enc, err := admin.EncryptSecret(key, secret)
	if err != nil {
		return err
	}

	database, err := db.Open(dsn)
	if err != nil {
		return err
	}
	defer database.Close()

	store := admin.NewDBStore(database)
	id, err := store.CreateAdmin(context.Background(), username, pwHash, enc)
	if err != nil {
		return err
	}

	uri := totp.ProvisioningURI(secret, username, "Pangolin")
	fmt.Println()
	fmt.Printf("✓ 已创建管理员 #%d: %s\n", id, username)
	fmt.Println()
	fmt.Println("请在身份验证器中添加以下 TOTP（二维码可由下方 otpauth URI 生成）：")
	fmt.Println("  Secret (Base32):", secret)
	fmt.Println("  otpauth URI    :", uri)
	fmt.Println()
	fmt.Println("登录需输入：用户名 + 密码 + 6 位动态验证码。")
	return nil
}

// readPassword reads a password from ADMIN_PASSWORD or, if unset, prompts on the
// terminal with echo disabled (falling back to a plain stdin line).
func readPassword() (string, error) {
	if v := os.Getenv("ADMIN_PASSWORD"); v != "" {
		return v, nil
	}
	fd := int(os.Stdin.Fd())
	if term.IsTerminal(fd) {
		fmt.Print("Password: ")
		b, err := term.ReadPassword(fd)
		fmt.Println()
		if err != nil {
			return "", err
		}
		return string(b), nil
	}
	r := bufio.NewReader(os.Stdin)
	line, err := r.ReadString('\n')
	if err != nil && line == "" {
		return "", err
	}
	return strings.TrimRight(line, "\r\n"), nil
}

// parseSecretKey accepts a 32-byte key as hex or base64 (mirrors the admin
// package's own loader so the CLI and server agree on the key format).
func parseSecretKey(s string) ([]byte, error) {
	s = strings.TrimSpace(s)
	if b, err := hex.DecodeString(s); err == nil && len(b) == 32 {
		return b, nil
	}
	if b, err := base64.StdEncoding.DecodeString(s); err == nil && len(b) == 32 {
		return b, nil
	}
	if b, err := base64.RawStdEncoding.DecodeString(s); err == nil && len(b) == 32 {
		return b, nil
	}
	return nil, errors.New("ADMIN_SECRET_KEY must decode to exactly 32 bytes (hex or base64)")
}
