// Gives every provisioned supervisor a way to open a closed terminal.
//
// A PIN cannot do it: the PIN route requires a session that already exists, so
// an outlet whose only POS accounts are PIN-only has no way in once back-office
// roles are refused. This backfills the username and password for supervisors
// created before that was understood.
//
// Cashiers are deliberately skipped. They sign on at a terminal a supervisor
// has already opened, so a password would be one more credential to leak for no
// capability gained.
//
//	go run ./scratch/possupervisorlogin plan
//	go run ./scratch/possupervisorlogin apply
package main

import (
	"crypto/rand"
	"fmt"
	"log"
	"math/big"
	"os"

	"nearle/models"
	"nearle/repositories"

	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func newPassword() string {
	// No l/I/O/0/1 — these get read off a screen and typed at a counter.
	const alphabet = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
	out := make([]byte, 14)
	for i := range out {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphabet))))
		if err != nil {
			log.Fatalf("generating a password: %v", err)
		}
		out[i] = alphabet[n.Int64()]
	}
	return string(out)
}

func main() {
	mode := "plan"
	if len(os.Args) > 1 {
		mode = os.Args[1]
	}

	_ = godotenv.Load()
	dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"),
		os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		log.Fatal(err)
	}
	repo := repositories.NewPosRepository(db)

	type target struct {
		Userid, Tenantid, Locationid int
		Fullname, Locationname       string
	}
	var targets []target
	db.Raw(`SELECT a.userid, a.tenantid, a.locationid,
	               TRIM(COALESCE(a.firstname,'')||' '||COALESCE(a.lastname,'')) AS fullname,
	               COALESCE(l.locationname,'') AS locationname
	        FROM app_users a
	        LEFT JOIN tenantlocations l
	               ON l.locationid = a.locationid AND l.tenantid = a.tenantid
	        WHERE COALESCE(a.roleid,0) = ?
	          AND (COALESCE(a.password,'') = '' OR COALESCE(a.authname,'') = '')
	          AND LOWER(COALESCE(a.status,'active')) <> 'inactive'
	        ORDER BY a.userid`, models.PosRoleSupervisor).Scan(&targets)

	if len(targets) == 0 {
		fmt.Println("Every supervisor already has a till login. Nothing to do.")
		return
	}

	fmt.Printf("supervisors with no way to open a closed terminal: %d\n\n", len(targets))

	for _, t := range targets {
		authname := fmt.Sprintf("supervisor.%d@pos.nearle.in", t.Locationid)
		password := newPassword()

		if mode != "apply" {
			fmt.Printf("  %-6d %-18s outlet %-6d %-26s -> %s / %s\n",
				t.Userid, t.Fullname, t.Locationid, t.Locationname, authname, password)
			continue
		}

		_, err := repo.UpdatePosUser(t.Tenantid, t.Locationid, models.PosUserRequest{
			Userid:   t.Userid,
			Authname: authname,
			Password: password,
		})
		if err != nil {
			fmt.Printf("  %-6d FAILED: %v\n", t.Userid, err)
			continue
		}

		// Prove it, rather than assert it — the whole point of this tool is that
		// a supervisor who cannot sign in is indistinguishable from one who can
		// until somebody stands at a counter and tries.
		session, err := repo.PosLogin(models.PosLoginRequest{
			Authname: authname,
			Password: password,
		})
		if err != nil {
			fmt.Printf("  %-6d written, but sign-in still fails: %v\n", t.Userid, err)
			continue
		}

		fmt.Printf("  %-6d %-18s outlet %-6d %-26s\n", t.Userid, t.Fullname, t.Locationid, t.Locationname)
		fmt.Printf("         login    %s / %s\n", authname, password)
		fmt.Printf("         opens as %s at %s, can_manage_staff=%v\n",
			session.Role, session.Locationname, session.Canmanagestaff)
	}

	if mode != "apply" {
		fmt.Println("\nNothing written — run `apply` to commit.")
	}
}
