// Reports who can actually sign in at an outlet, and by which of the two ways.
//
// Read-only. Answers the question a shop asks on day one — "what do I type into
// the till?" — by separating the two credentials that exist, because they are
// not interchangeable:
//
//   - a password opens a *closed* terminal, and only a supervisor's does
//     anything useful, because the shell it opens is decided by the account;
//
//   - a PIN switches operator on a terminal that is *already open*, and is
//     useless on its own.
//
//     go run ./scratch/poslogins 1087 1135
package main

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

	"nearle/models"

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

type row struct {
	Userid                        int
	Fullname, Authname, Contactno string
	Password                      string
	Pin                           int64
	Roleid, Configid              int
	Status                        string
}

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

	_ = 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)
	}

	// `top` ranks outlets by what a till would actually have to sell, so a demo
	// is pointed at a shop with a catalogue rather than at one that opens empty.
	if len(os.Args) > 1 && os.Args[1] == "top" {
		type outletRow struct {
			Tenantid, Locationid int
			Tenantname, Locname  string
			Products, Withpasswd int
		}
		var top []outletRow
		db.Raw(`
			SELECT t.tenantid, l.locationid,
			       COALESCE(t.tenantname,'')   AS tenantname,
			       COALESCE(l.locationname,'') AS locname,
			       COUNT(DISTINCT a.productid) AS products,
			       (SELECT COUNT(*) FROM app_users u
			         WHERE u.tenantid = t.tenantid
			           AND COALESCE(u.locationid,0) IN (l.locationid, 0)
			           AND COALESCE(u.password,'') <> ''
			           AND LOWER(COALESCE(u.status,'')) <> 'inactive') AS withpasswd
			FROM tenantlocations l
			JOIN tenants t ON t.tenantid = l.tenantid
			JOIN productlocations b ON b.locationid = l.locationid AND b.tenantid = l.tenantid
			JOIN products a ON a.productid = b.productid AND a.tenantid = b.tenantid
			GROUP BY t.tenantid, l.locationid, t.tenantname, l.locationname
			ORDER BY products DESC LIMIT 400`).Scan(&top)

		fmt.Printf("%-8s %-10s %-22s %-26s %-9s %s\n",
			"tenant", "outlet", "tenant name", "outlet name", "products", "can sign in")
		shown := 0
		for _, o := range top {
			// Only outlets a person can actually open. A big catalogue behind a
			// till nobody can sign in to is not a candidate for anything.
			if o.Withpasswd == 0 || shown >= 12 {
				continue
			}
			shown++
			fmt.Printf("%-8d %-10d %-22s %-26s %-9d %d\n",
				o.Tenantid, o.Locationid, trunc(o.Tenantname, 22),
				trunc(o.Locname, 26), o.Products, o.Withpasswd)
		}
		return
	}

	var tenant, outlet string
	db.Raw(`SELECT COALESCE(tenantname,'') FROM tenants WHERE tenantid=?`, tenantID).Scan(&tenant)
	db.Raw(`SELECT COALESCE(locationname,'') FROM tenantlocations WHERE locationid=? AND tenantid=?`,
		locationID, tenantID).Scan(&outlet)
	if outlet == "" {
		log.Fatalf("tenant %d has no outlet %d", tenantID, locationID)
	}

	// Counted the way /pos/catalogue counts them — products joined to this
	// outlet — rather than per tenant. A tenant with a full catalogue can still
	// have an outlet that stocks none of it, and that outlet's till opens empty.
	var products int64
	db.Raw(`SELECT COUNT(*)
	        FROM products a
	        INNER JOIN productlocations b
	                ON a.productid = b.productid AND a.tenantid = b.tenantid
	        WHERE a.tenantid = ? AND b.locationid = ?`, tenantID, locationID).Scan(&products)

	fmt.Printf("tenant %d  %s\noutlet %d  %s\nproducts stocked at this outlet: %d\n",
		tenantID, tenant, locationID, outlet, products)

	// Everyone the outlet can see. locationid 0 is a tenant-wide account — a
	// proprietor who is not pinned to one shop — and those can open any of
	// their outlets, so they belong in this list too.
	var rows []row
	db.Raw(`
		SELECT userid,
		       TRIM(COALESCE(firstname,'') || ' ' || COALESCE(lastname,'')) AS fullname,
		       COALESCE(authname,'')  AS authname,
		       COALESCE(contactno,'') AS contactno,
		       COALESCE(password,'')  AS password,
		       COALESCE(pin,0)        AS pin,
		       COALESCE(roleid,0)     AS roleid,
		       COALESCE(configid,0)   AS configid,
		       COALESCE(status,'')    AS status
		FROM app_users
		WHERE tenantid = ?
		  AND COALESCE(locationid,0) IN (?, 0)
		  AND LOWER(COALESCE(status,'')) <> 'inactive'
		ORDER BY userid`, tenantID, locationID).Scan(&rows)

	fmt.Printf("\n=== PASSWORD SIGN-IN  (POST /v1/pos/login) — opens a closed terminal ===\n")
	fmt.Printf("%-7s %-24s %-30s %-12s %-6s %s\n",
		"userid", "name", "authname (the username)", "role", "shell", "password")
	any := false
	for _, r := range rows {
		if strings.TrimSpace(r.Password) == "" {
			continue
		}
		any = true
		shell := "cashier"
		if models.PosRoleCanManageStaff(r.Roleid) {
			shell = "SUPER"
		}
		role := models.PosRoleName(r.Roleid)
		if role == "" {
			role = fmt.Sprintf("(roleid %d)", r.Roleid)
		}
		id := r.Authname
		if id == "" {
			id = r.Contactno + "  (phone)"
		}
		fmt.Printf("%-7d %-24s %-30s %-12s %-6s %s\n",
			r.Userid, trunc(r.Fullname, 24), trunc(id, 30), role, shell, r.Password)
	}
	if !any {
		fmt.Println("  (nobody at this outlet has a password — the till cannot be opened)")
	}

	fmt.Printf("\n=== PIN SIGN-IN  (POST /v1/pos/login/pin) — switches operator, terminal already open ===\n")
	fmt.Printf("%-7s %-24s %-12s %-6s %s\n", "userid", "name", "role", "shell", "pin")
	any = false
	seen := map[int64]int{}
	for _, r := range rows {
		if r.Pin == 0 {
			continue
		}
		seen[r.Pin]++
	}
	for _, r := range rows {
		if r.Pin == 0 {
			continue
		}
		any = true
		shell := "cashier"
		if models.PosRoleCanManageStaff(r.Roleid) {
			shell = "SUPER"
		}
		role := models.PosRoleName(r.Roleid)
		if role == "" {
			role = fmt.Sprintf("(roleid %d)", r.Roleid)
		}
		note := ""
		if seen[r.Pin] > 1 {
			note = "  <- DUPLICATE, refused at sign-in"
		}
		if r.Pin < 1000 {
			note = "  <- under 4 digits, cannot be typed"
		}
		fmt.Printf("%-7d %-24s %-12s %-6s %04d%s\n",
			r.Userid, trunc(r.Fullname, 24), role, shell, r.Pin, note)
	}
	if !any {
		fmt.Println("  (nobody at this outlet has a PIN)")
	}
	fmt.Println()
}

func trunc(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n-1] + "…"
}
