// Proves POS sign-in against the live database, read-only.
//
// Written because the interesting half of this feature is not the token — that
// has unit tests — but whether the *account model* actually holds up against
// real rows: does a shop's user resolve to the right tenant, does the outlet
// list come back non-empty, and does an account from one tenant get refused at
// another tenant's outlet.
//
// Passwords are read out of the database and handed straight back into the
// login so the happy path can be proven without anyone typing or printing one.
// Nothing here is ever echoed.
//
//	go run ./scratch/posloginproof users 1087    # who could open a till
//	go run ./scratch/posloginproof login 1087    # sign the first one in
//	go run ./scratch/posloginproof cross         # refuse another tenant's outlet
package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"nearle/models"
	"nearle/repositories"

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

func main() {
	mode := "users"
	if len(os.Args) > 1 {
		mode = os.Args[1]
	}
	tenantID := 1087
	if len(os.Args) > 2 {
		tenantID, _ = strconv.Atoi(os.Args[2])
	}

	_ = godotenv.Load()
	if strings.TrimSpace(os.Getenv("POS_TOKEN_SECRET")) == "" {
		// Only needed by the service layer; the repository probes below work
		// without it. Set a throwaway so `login` can mint.
		os.Setenv("POS_TOKEN_SECRET", "scratch-proof-signing-key-not-real")
	}

	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)

	switch mode {
	case "users":
		listUsers(db, tenantID)
	case "login":
		proveLogin(db, repo, tenantID)
	case "cross":
		proveCrossTenantRefusal(db, repo)
	default:
		log.Fatalf("unknown mode %q", mode)
	}
}

// listUsers shows who could open a till for a tenant, and whether their record
// is complete enough to do it.
func listUsers(db *gorm.DB, tenantID int) {
	type row struct {
		Userid     int
		Authname   string
		Roleid     int
		Status     string
		Locationid int
		Haspw      bool
	}

	var rows []row
	err := db.Raw(`
		SELECT userid, COALESCE(authname,'') AS authname, COALESCE(roleid,0) AS roleid,
		       COALESCE(status,'') AS status, COALESCE(locationid,0) AS locationid,
		       (COALESCE(password,'') <> '') AS haspw
		FROM app_users WHERE tenantid = ? ORDER BY userid`, tenantID).Scan(&rows).Error
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("tenant %d — %d user(s)\n", tenantID, len(rows))
	fmt.Printf("%-8s %-34s %-6s %-10s %-10s %s\n", "userid", "authname", "role", "status", "location", "password set")
	fmt.Println(strings.Repeat("-", 92))
	for _, r := range rows {
		loc := "any"
		if r.Locationid > 0 {
			loc = strconv.Itoa(r.Locationid)
		}
		fmt.Printf("%-8d %-34s %-6d %-10s %-10s %v\n",
			r.Userid, r.Authname, r.Roleid, r.Status, loc, r.Haspw)
	}

	var locs []struct {
		Locationid   int
		Locationname string
		Status       string
	}
	db.Raw(`SELECT locationid, COALESCE(locationname,'') AS locationname,
	               COALESCE(status,'') AS status
	        FROM tenantlocations WHERE tenantid = ? ORDER BY locationid`, tenantID).Scan(&locs)

	fmt.Printf("\noutlets: %d\n", len(locs))
	for _, l := range locs {
		fmt.Printf("  %-8d %-40s %s\n", l.Locationid, l.Locationname, l.Status)
	}
}

// proveLogin signs in the tenant's first usable account and reports what the
// session resolved to.
func proveLogin(db *gorm.DB, repo repositories.PosRepository, tenantID int) {
	// An explicit account, when the interesting case is a particular one — the
	// till's own outlet, or a proprietor who reaches several.
	wanted := ""
	if len(os.Args) > 3 {
		wanted = strings.TrimSpace(os.Args[3])
	}

	var cred struct {
		Authname string
		Password string
	}
	query := `
		SELECT COALESCE(authname,'') AS authname, COALESCE(password,'') AS password
		FROM app_users
		WHERE tenantid = ? AND COALESCE(password,'') <> '' AND COALESCE(authname,'') <> ''
		  AND LOWER(COALESCE(status,'active')) <> 'inactive'`
	params := []interface{}{tenantID}
	if wanted != "" {
		query += ` AND LOWER(authname) = LOWER(?)`
		params = append(params, wanted)
	}
	query += ` ORDER BY userid LIMIT 1`

	err := db.Raw(query, params...).Scan(&cred).Error
	if err != nil {
		log.Fatal(err)
	}
	if cred.Authname == "" {
		log.Fatalf("tenant %d has no active account with a password set", tenantID)
	}

	fmt.Printf("signing in %s (password read from the database, not printed)\n\n", cred.Authname)

	session, err := repo.PosLogin(models.PosLoginRequest{
		Authname:   cred.Authname,
		Password:   cred.Password,
		Terminalid: "T5EDD",
	})
	if err != nil {
		log.Fatalf("REFUSED: %v", err)
	}

	fmt.Printf("  user        %d  %s\n", session.Userid, session.Fullname)
	fmt.Printf("  tenant      %d  %s\n", session.Tenantid, session.Tenantname)
	fmt.Printf("  store_id    %s\n", session.Storeid)
	fmt.Printf("  outlet      %d  %s\n", session.Locationid, session.Locationname)
	fmt.Printf("  gstin       %s\n", session.Gstin)
	fmt.Printf("  outlets     %d reachable\n", len(session.Locations))
	for _, l := range session.Locations {
		fmt.Printf("                %-8d %s\n", l.Locationid, l.Locationname)
	}

	// The check that matters: a wrong password must be refused, and refused
	// with the same message a wrong email gets.
	if _, err := repo.PosLogin(models.PosLoginRequest{
		Authname: cred.Authname, Password: cred.Password + "x",
	}); err == nil {
		fmt.Println("\n  !! a wrong password was ACCEPTED")
	} else {
		fmt.Printf("\n  wrong password refused: %v\n", err)
	}

	if _, err := repo.PosLogin(models.PosLoginRequest{
		Authname: "nobody@nowhere.invalid", Password: "whatever",
	}); err != nil {
		fmt.Printf("  unknown account refused: %v\n", err)
	}
}

// proveCrossTenantRefusal is the authorisation test: an account from one tenant
// must not be able to open a till at another tenant's outlet, which is exactly
// what a till could do before by editing one field in Settings.
func proveCrossTenantRefusal(db *gorm.DB, repo repositories.PosRepository) {
	var cred struct {
		Authname string
		Password string
		Tenantid int
	}
	err := db.Raw(`
		SELECT COALESCE(a.authname,'') AS authname, COALESCE(a.password,'') AS password, a.tenantid
		FROM app_users a
		WHERE COALESCE(a.password,'') <> '' AND COALESCE(a.authname,'') <> '' AND a.tenantid > 0
		  AND LOWER(COALESCE(a.status,'active')) <> 'inactive'
		ORDER BY a.userid LIMIT 1`).Scan(&cred).Error
	if err != nil || cred.Authname == "" {
		log.Fatalf("no usable account to test with: %v", err)
	}

	// Somebody else's outlet.
	var foreign int
	db.Raw(`SELECT locationid FROM tenantlocations WHERE tenantid <> ? ORDER BY locationid LIMIT 1`,
		cred.Tenantid).Scan(&foreign)
	if foreign == 0 {
		log.Fatal("only one tenant has outlets; nothing to cross")
	}

	fmt.Printf("account belongs to tenant %d; asking for outlet %d, which does not\n\n",
		cred.Tenantid, foreign)

	if _, err := repo.PosLogin(models.PosLoginRequest{
		Authname: cred.Authname, Password: cred.Password, Locationid: foreign,
	}); err == nil {
		fmt.Println("  !! ACCEPTED — a tenant signed a till into another tenant's outlet")
	} else {
		fmt.Printf("  refused: %v\n", err)
	}

	allowed, err := repo.PosLocationAllowed(cred.Tenantid, foreign)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("  PosLocationAllowed(tenant %d, outlet %d) = %v (want false)\n",
		cred.Tenantid, foreign, allowed)
}
