package main

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"flag"
	"fmt"
	"os"

	"golang.org/x/crypto/bcrypt"

	"git.fabledsword.com/bvandeusen/minstrel/internal/config"
	"git.fabledsword.com/bvandeusen/minstrel/internal/db"
	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
	"git.fabledsword.com/bvandeusen/minstrel/internal/logging"
)

// runMigrate applies the embedded migrations and exits. The server also
// auto-migrates on startup (main.go); this standalone path exists for CI
// (provision a fresh DB before the integration suite) and operators who
// want to migrate without starting the server.
func runMigrate(args []string) error {
	fs := flag.NewFlagSet("migrate", flag.ContinueOnError)
	configPath := fs.String("config", os.Getenv("MINSTREL_CONFIG"), "path to YAML config file")
	if err := fs.Parse(args); err != nil {
		return err
	}
	cfg, err := config.Load(*configPath)
	if err != nil {
		return fmt.Errorf("load config: %w", err)
	}
	logger, err := logging.New(os.Stdout, cfg.Log.Level, cfg.Log.Format)
	if err != nil {
		return fmt.Errorf("init logger: %w", err)
	}
	if err := db.Migrate(cfg.Database.URL, logger); err != nil {
		return fmt.Errorf("migrate: %w", err)
	}
	fmt.Println("minstrel: migrations applied")
	return nil
}

// runAdmin dispatches `minstrel admin <subcommand>`.
func runAdmin(args []string) error {
	if len(args) == 0 {
		return errors.New("usage: minstrel admin reset-password [-user NAME] [-password PW] [-config PATH]")
	}
	switch args[0] {
	case "reset-password":
		return adminResetPassword(args[1:])
	default:
		return fmt.Errorf("unknown admin subcommand %q", args[0])
	}
}

// adminResetPassword resets a user's credentials. It updates BOTH
// password_hash (bcrypt, for /api/auth/login) and subsonic_password
// (plaintext, required for Subsonic t+s token verification) so neither
// auth path is left stale. Recovers a locked-out operator when the
// bootstrap password was missed or the DB volume was recreated (Fable
// #321) without DB surgery.
func adminResetPassword(args []string) error {
	fs := flag.NewFlagSet("admin reset-password", flag.ContinueOnError)
	configPath := fs.String("config", os.Getenv("MINSTREL_CONFIG"), "path to YAML config file")
	username := fs.String("user", "admin", "username to reset")
	password := fs.String("password", "", "new password; if empty a strong one is generated and printed")
	if err := fs.Parse(args); err != nil {
		return err
	}

	cfg, err := config.Load(*configPath)
	if err != nil {
		return fmt.Errorf("load config: %w", err)
	}
	if cfg.Database.URL == "" {
		return errors.New("no database URL configured (set it in the config file or MINSTREL_DATABASE_URL)")
	}

	ctx := context.Background()
	pool, err := db.Open(ctx, cfg.Database.URL)
	if err != nil {
		return fmt.Errorf("open db: %w", err)
	}
	defer pool.Close()
	q := dbq.New(pool)

	user, err := q.GetUserByUsername(ctx, *username)
	if err != nil {
		return fmt.Errorf("look up user %q: %w", *username, err)
	}

	pw := *password
	generated := false
	if pw == "" {
		b := make([]byte, 18)
		if _, err := rand.Read(b); err != nil {
			return fmt.Errorf("generate password: %w", err)
		}
		pw = base64.RawURLEncoding.EncodeToString(b)
		generated = true
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
	if err != nil {
		return fmt.Errorf("hash password: %w", err)
	}
	if err := q.ChangeUserPassword(ctx, dbq.ChangeUserPasswordParams{
		ID:           user.ID,
		PasswordHash: string(hash),
	}); err != nil {
		return fmt.Errorf("update password_hash: %w", err)
	}
	sp := pw
	if err := q.SetSubsonicPassword(ctx, dbq.SetSubsonicPasswordParams{
		ID:               user.ID,
		SubsonicPassword: &sp,
	}); err != nil {
		return fmt.Errorf("update subsonic_password: %w", err)
	}

	if generated {
		fmt.Printf("minstrel: password for %q reset.\nNew password: %s\n", *username, pw)
	} else {
		fmt.Printf("minstrel: password for %q reset.\n", *username)
	}
	return nil
}
