package main

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"fn-registry/functions/infra"
)

type User struct {
	ID          string `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Color       string `json:"color"`
	IsAdmin     bool   `json:"is_admin"`
	CreatedAt   string `json:"created_at"`
}

var (
	errUserNotFound      = errors.New("user not found")
	errUserAlreadyExists = errors.New("username already exists")
	errInvalidCredentials = errors.New("invalid credentials")
)

func (db *DB) CreateUser(username, password, displayName string) (*User, error) {
	username = strings.TrimSpace(strings.ToLower(username))
	if username == "" {
		return nil, fmt.Errorf("username required")
	}
	if len(password) < 4 {
		return nil, fmt.Errorf("password must be at least 4 characters")
	}
	hash, err := infra.PasswordHash(password, 0)
	if err != nil {
		return nil, fmt.Errorf("hash: %w", err)
	}
	u := User{ID: newID(), Username: username, DisplayName: displayName, CreatedAt: nowRFC3339()}
	_, err = db.conn.Exec(
		`INSERT INTO users (id, username, password_hash, display_name, created_at) VALUES (?, ?, ?, ?, ?)`,
		u.ID, u.Username, hash, u.DisplayName, u.CreatedAt,
	)
	if err != nil {
		if strings.Contains(err.Error(), "UNIQUE") {
			return nil, errUserAlreadyExists
		}
		return nil, err
	}
	return &u, nil
}

func (db *DB) GetUserByID(id string) (*User, error) {
	var u User
	var isAdmin int
	err := db.conn.QueryRow(
		`SELECT id, username, display_name, color, is_admin, created_at FROM users WHERE id=?`, id,
	).Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &isAdmin, &u.CreatedAt)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, errUserNotFound
	}
	if err != nil {
		return nil, err
	}
	u.IsAdmin = isAdmin == 1
	return &u, nil
}

func (db *DB) IsAdmin(userID string) (bool, error) {
	if userID == "" {
		return false, nil
	}
	var n int
	err := db.conn.QueryRow(`SELECT COALESCE(is_admin, 0) FROM users WHERE id=?`, userID).Scan(&n)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}
	return n == 1, err
}

func (db *DB) GetUserByUsername(username string) (*User, string, error) {
	username = strings.TrimSpace(strings.ToLower(username))
	var u User
	var hash string
	var isAdmin int
	err := db.conn.QueryRow(
		`SELECT id, username, display_name, color, is_admin, created_at, password_hash FROM users WHERE username=?`, username,
	).Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &isAdmin, &u.CreatedAt, &hash)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, "", errUserNotFound
	}
	if err != nil {
		return nil, "", err
	}
	u.IsAdmin = isAdmin == 1
	return &u, hash, nil
}

func (db *DB) ListUsers() ([]User, error) {
	rows, err := db.conn.Query(`SELECT id, username, display_name, color, is_admin, created_at FROM users ORDER BY username`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []User{}
	for rows.Next() {
		var u User
		var isAdmin int
		if err := rows.Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &isAdmin, &u.CreatedAt); err != nil {
			return nil, err
		}
		u.IsAdmin = isAdmin == 1
		out = append(out, u)
	}
	return out, rows.Err()
}

func (db *DB) Authenticate(username, password string) (*User, error) {
	u, hash, err := db.GetUserByUsername(username)
	if err != nil {
		if errors.Is(err, errUserNotFound) {
			return nil, errInvalidCredentials
		}
		return nil, err
	}
	if err := infra.PasswordVerify(password, hash); err != nil {
		return nil, errInvalidCredentials
	}
	return u, nil
}

func (db *DB) CountUsers() (int, error) {
	var n int
	if err := db.conn.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&n); err != nil {
		return 0, err
	}
	return n, nil
}

func (db *DB) UpdateUserColor(id, color string) error {
	_, err := db.conn.Exec(`UPDATE users SET color=? WHERE id=?`, color, id)
	return err
}

func (db *DB) DeleteSessionByToken(token string) error {
	_, err := db.conn.Exec(`DELETE FROM sessions WHERE token=?`, token)
	return err
}
