// Proves the till and the Nearle Daily application no longer share accounts.
//
// Read-only. Four claims, each checked against live rows rather than asserted:
//
//  1. a provisioned supervisor can open a closed terminal;
//
//  2. a back-office account cannot, however senior it is;
//
//  3. a till account cannot reach the Nearle Daily application; and
//
//  4. a till account is not listed as though it were an app user.
//
//     go run ./scratch/posseparation
package main

import (
	"fmt"
	"os"
	"strings"

	"nearle/models"
	"nearle/repositories"

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

var failures int

func check(claim string, ok bool, detail string) {
	mark := "PASS"
	if !ok {
		mark = "FAIL"
		failures++
	}
	fmt.Printf("  [%s] %s\n         %s\n", mark, claim, detail)
}

func main() {
	_ = 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 {
		fmt.Println(err)
		os.Exit(1)
	}
	pos := repositories.NewPosRepository(db)
	users := repositories.NewUserRepository(db)

	// A real provisioned supervisor, and its password, read back out.
	var sup struct {
		Userid                       int
		Authname, Password           string
		Configid, Tenantid, Location int
	}
	db.Raw(`SELECT userid, COALESCE(authname,'') authname, COALESCE(password,'') password,
	               COALESCE(configid,0) configid, COALESCE(tenantid,0) tenantid,
	               COALESCE(locationid,0) location
	        FROM app_users
	        WHERE COALESCE(roleid,0) = ? AND COALESCE(authname,'') <> ''
	        ORDER BY userid LIMIT 1`, models.PosRoleSupervisor).Scan(&sup)
	if sup.Userid == 0 {
		fmt.Println("no provisioned supervisor to test with")
		os.Exit(1)
	}

	fmt.Printf("supervisor under test: %d  %s  (tenant %d, outlet %d)\n\n",
		sup.Userid, sup.Authname, sup.Tenantid, sup.Location)

	fmt.Println("1. a provisioned supervisor opens a closed terminal")
	session, err := pos.PosLogin(models.PosLoginRequest{
		Authname: sup.Authname, Password: sup.Password,
	})
	check("supervisor signs in at the till",
		err == nil && session != nil,
		fmt.Sprintf("err=%v", err))
	if session != nil {
		check("and gets the supervisor shell",
			session.Canmanagestaff && session.Roleid == models.PosRoleSupervisor,
			fmt.Sprintf("role=%s can_manage_staff=%v", session.Role, session.Canmanagestaff))
	}

	fmt.Println("\n2. back-office accounts cannot open a terminal at all")
	var backOffice []struct {
		Userid             int
		Authname, Password string
		Roleid             int
	}
	db.Raw(`SELECT userid, COALESCE(authname,'') authname, COALESCE(password,'') password,
	               COALESCE(roleid,0) roleid
	        FROM app_users
	        WHERE COALESCE(roleid,0) IN (1,2,3,4,5,6)
	          AND COALESCE(authname,'') <> '' AND COALESCE(password,'') <> ''
	          AND LOWER(COALESCE(status,'active')) <> 'inactive'
	        ORDER BY userid LIMIT 5`).Scan(&backOffice)
	for _, b := range backOffice {
		_, err := pos.PosLogin(models.PosLoginRequest{
			Authname: b.Authname, Password: b.Password,
		})
		check(fmt.Sprintf("roleid %d (%s) refused at the till", b.Roleid, b.Authname),
			err != nil && strings.Contains(err.Error(), "not set up for the till"),
			fmt.Sprintf("err=%v", err))
	}

	fmt.Println("\n3. a till account cannot reach the Nearle Daily application")
	uid, _, _ := users.GetUserByAuthname(sup.Authname, sup.Configid)
	check("applogin lookup does not find the supervisor",
		uid == 0,
		fmt.Sprintf("GetUserByAuthname(%s) -> userid %d", sup.Authname, uid))

	uid2, _, _, _ := users.GetUserLogin("authname", sup.Authname, sup.Configid)
	check("tenant web login does not find the supervisor",
		uid2 == 0,
		fmt.Sprintf("GetUserLogin(%s) -> userid %d", sup.Authname, uid2))

	uid3, _ := users.FindUserID(sup.Authname, "", sup.Configid)
	check("password-setup lookup does not find the supervisor",
		uid3 == 0,
		fmt.Sprintf("FindUserID(%s) -> userid %d", sup.Authname, uid3))

	fmt.Println("\n4. till accounts are not listed as app users")
	list, err := users.GetAllUsers(0, sup.Tenantid, 1, 500, "")
	leaked := 0
	for _, u := range list {
		if u.Roleid == models.PosRoleSupervisor || u.Roleid == models.PosRoleCashier {
			leaked++
		}
	}
	check("getallusers hides till accounts",
		err == nil && leaked == 0,
		fmt.Sprintf("%d of %d rows were till accounts", leaked, len(list)))

	// ...but the POS console can still read its own people by asking for them.
	sups, err := users.GetAllUsers(models.PosRoleSupervisor, sup.Tenantid, 1, 500, "")
	check("asking for role 7 explicitly still works",
		err == nil && len(sups) > 0,
		fmt.Sprintf("%d supervisor(s) returned", len(sups)))

	fmt.Println()
	if failures > 0 {
		fmt.Printf("%d CHECK(S) FAILED\n", failures)
		os.Exit(1)
	}
	fmt.Println("all checks passed")
}
