package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"golang.org/x/crypto/bcrypt"
)

type User struct {
	ID        int64      `json:"id"`
	Username  string     `json:"username"`
	Role      string     `json:"role"`
	Status    string     `json:"status"`
	FullName  string     `json:"full_name"`
	Email     string     `json:"email"`
	LastLogin *time.Time `json:"last_login"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
}

// seedDefaultUsers inserts the three built-in accounts on first boot.
func (db *DB) seedDefaultUsers(ctx context.Context) {
	defaults := []struct{ username, password, role, fullName string }{
		{"admin",    "admin123",  "admin",    "System Administrator"},
		{"operator", "op123",     "operator", "Operations User"},
		{"viewer",   "view123",   "viewer",   "Read-Only Viewer"},
	}
	for _, d := range defaults {
		var exists bool
		db.pool.QueryRow(ctx,
			`SELECT EXISTS(SELECT 1 FROM users WHERE username=$1)`, d.username,
		).Scan(&exists)
		if exists {
			continue
		}
		hash, err := bcrypt.GenerateFromPassword([]byte(d.password), bcrypt.DefaultCost)
		if err != nil {
			log.Printf("seed user %s: %v", d.username, err)
			continue
		}
		db.pool.Exec(ctx,
			`INSERT INTO users (username, password_hash, role, full_name)
			 VALUES ($1,$2,$3,$4) ON CONFLICT (username) DO NOTHING`,
			d.username, string(hash), d.role, d.fullName,
		)
		log.Printf("Seeded user: %s (%s)", d.username, d.role)
	}
}

// verifyUser returns the User if username+password match, else nil.
func (db *DB) verifyUser(ctx context.Context, username, password string) (*User, error) {
	var u User
	var hash string
	err := db.pool.QueryRow(ctx,
		`SELECT id, username, password_hash, role, status,
		        COALESCE(full_name,''), COALESCE(email,''), last_login, created_at, updated_at
		 FROM users WHERE username=$1`, username,
	).Scan(&u.ID, &u.Username, &hash, &u.Role, &u.Status,
		&u.FullName, &u.Email, &u.LastLogin, &u.CreatedAt, &u.UpdatedAt)
	if err != nil {
		return nil, fmt.Errorf("user not found")
	}
	if u.Status == "disabled" {
		return nil, fmt.Errorf("account disabled")
	}
	if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
		return nil, fmt.Errorf("invalid credentials")
	}
	return &u, nil
}

// updateLastLogin stamps the login time without blocking.
func (db *DB) updateLastLogin(ctx context.Context, username string) {
	db.pool.Exec(ctx,
		`UPDATE users SET last_login=NOW(), updated_at=NOW() WHERE username=$1`, username)
}

// listUsers returns all users (password hash excluded).
func (db *DB) listUsers(ctx context.Context) ([]User, error) {
	rows, err := db.pool.Query(ctx,
		`SELECT id, username, role, status,
		        COALESCE(full_name,''), COALESCE(email,''), last_login, created_at, updated_at
		 FROM users ORDER BY id ASC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []User
	for rows.Next() {
		var u User
		if err := rows.Scan(&u.ID, &u.Username, &u.Role, &u.Status,
			&u.FullName, &u.Email, &u.LastLogin, &u.CreatedAt, &u.UpdatedAt); err != nil {
			continue
		}
		out = append(out, u)
	}
	return out, nil
}

// createUser inserts a new user with a bcrypt-hashed password.
func (db *DB) createUser(ctx context.Context, username, password, role, fullName, email string) (*User, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}
	var u User
	err = db.pool.QueryRow(ctx,
		`INSERT INTO users (username, password_hash, role, full_name, email)
		 VALUES ($1,$2,$3,$4,$5)
		 RETURNING id, username, role, status,
		           COALESCE(full_name,''), COALESCE(email,''), last_login, created_at, updated_at`,
		username, string(hash), role, nullText(fullName), nullText(email),
	).Scan(&u.ID, &u.Username, &u.Role, &u.Status,
		&u.FullName, &u.Email, &u.LastLogin, &u.CreatedAt, &u.UpdatedAt)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

// updateUser changes role, status, full_name and email for the given user ID.
func (db *DB) updateUser(ctx context.Context, id int64, role, status, fullName, email string) (*User, error) {
	var u User
	err := db.pool.QueryRow(ctx,
		`UPDATE users
		 SET role=$2, status=$3, full_name=NULLIF($4,''), email=NULLIF($5,''), updated_at=NOW()
		 WHERE id=$1
		 RETURNING id, username, role, status,
		           COALESCE(full_name,''), COALESCE(email,''), last_login, created_at, updated_at`,
		id, role, status, fullName, email,
	).Scan(&u.ID, &u.Username, &u.Role, &u.Status,
		&u.FullName, &u.Email, &u.LastLogin, &u.CreatedAt, &u.UpdatedAt)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

// deleteUser removes a user by ID. Returns error if user not found.
func (db *DB) deleteUser(ctx context.Context, id int64) error {
	ct, err := db.pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, id)
	if err != nil {
		return err
	}
	if ct.RowsAffected() == 0 {
		return fmt.Errorf("user not found")
	}
	return nil
}

// resetPassword sets a new bcrypt-hashed password for the given user ID.
func (db *DB) resetPassword(ctx context.Context, id int64, newPassword string) error {
	hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	ct, err := db.pool.Exec(ctx,
		`UPDATE users SET password_hash=$2, updated_at=NOW() WHERE id=$1`,
		id, string(hash))
	if err != nil {
		return err
	}
	if ct.RowsAffected() == 0 {
		return fmt.Errorf("user not found")
	}
	return nil
}
