// luminary-recover: emergency recovery CLI (reset password, clear IP rules, restart).
// Intended for docker exec when admin login is locked out.
package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/rose_cat707/luminary/internal/auth"
	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/settings"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func main() {
	os.Exit(run())
}

func run() int {
	dataDir := flag.String("data-dir", envOr("LUMINARY_DATA", "./data"), "directory containing luminary.db")
	deprecatedConfig := flag.String("config", "", "deprecated: ignored; use -data-dir / LUMINARY_DATA")
	username := flag.String("username", "", "admin username (default: admin)")
	password := flag.String("password", "", "new admin password (default: admin123)")
	doAll := flag.Bool("all", false, "reset password, clear IP rules and sessions, then restart")
	doPassword := flag.Bool("reset-password", false, "reset admin password")
	doIP := flag.Bool("clear-ip", false, "delete all IP rules")
	doSessions := flag.Bool("clear-sessions", false, "delete all admin sessions")
	doRestart := flag.Bool("restart", false, "send SIGTERM to PID 1 to restart server (clears login cooldown)")
	flag.Parse()
	if *deprecatedConfig != "" {
		fmt.Fprintln(os.Stderr, "warning: -config is deprecated and ignored; use -data-dir / LUMINARY_DATA")
	}

	if !*doAll && !*doPassword && !*doIP && !*doSessions && !*doRestart {
		fmt.Fprintln(os.Stderr, "usage: luminary-recover --all [--password NEW] [--restart]")
		fmt.Fprintln(os.Stderr, "       luminary-recover --reset-password --clear-ip --clear-sessions [--restart]")
		flag.PrintDefaults()
		return 2
	}

	user := *username
	pass := *password
	if user == "" || pass == "" {
		legacyUser, legacyPass := settings.LegacyAdminCredentials()
		if user == "" {
			user = legacyUser
		}
		if pass == "" {
			pass = legacyPass
		}
	}

	if *doAll {
		*doPassword = true
		*doIP = true
		*doSessions = true
		*doRestart = true
	}

	dbPath := filepath.Join(*dataDir, "luminary.db")
	db, err := openDB(dbPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "open database: %v\n", err)
		return 1
	}

	if *doPassword {
		if pass == "" {
			fmt.Fprintln(os.Stderr, "password is required (use --password)")
			return 1
		}
		if err := resetPassword(db, user, pass); err != nil {
			fmt.Fprintf(os.Stderr, "reset password: %v\n", err)
			return 1
		}
		fmt.Printf("admin password reset for user %q\n", user)
	}

	if *doIP {
		res := db.Exec("DELETE FROM ip_rules")
		if res.Error != nil {
			fmt.Fprintf(os.Stderr, "clear IP rules: %v\n", res.Error)
			return 1
		}
		fmt.Printf("cleared %d IP rule(s)\n", res.RowsAffected)
	}

	if *doSessions {
		res := db.Exec("DELETE FROM admin_sessions")
		if res.Error != nil {
			fmt.Fprintf(os.Stderr, "clear sessions: %v\n", res.Error)
			return 1
		}
		fmt.Printf("cleared %d admin session(s)\n", res.RowsAffected)
	}

	if *doRestart {
		fmt.Println("sending SIGTERM to PID 1 (restart server to clear in-memory login cooldown)...")
		if err := syscall.Kill(1, syscall.SIGTERM); err != nil {
			fmt.Fprintf(os.Stderr, "restart: %v (you may need to restart the container manually)\n", err)
			return 1
		}
	}

	fmt.Println("done")
	return 0
}

func openDB(path string) (*gorm.DB, error) {
	dsn := path + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		return nil, err
	}
	sqlDB, err := db.DB()
	if err != nil {
		return nil, err
	}
	sqlDB.SetMaxOpenConns(1)
	return db, nil
}

func resetPassword(db *gorm.DB, username, password string) error {
	hash, err := auth.HashPassword(password)
	if err != nil {
		return err
	}
	var user model.AdminUser
	err = db.Where("username = ?", username).First(&user).Error
	if err == gorm.ErrRecordNotFound {
		return db.Create(&model.AdminUser{
			Username:     username,
			PasswordHash: hash,
			CreatedAt:    time.Now(),
		}).Error
	}
	if err != nil {
		return err
	}
	return db.Model(&user).Update("password_hash", hash).Error
}

func envOr(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}
