package store

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"
)

// CreateUser inserts a new user. The caller is responsible for
// generating an ID (typically a ULID) and hashing the password.
func (s *Store) CreateUser(ctx context.Context, u User) error {
	_, err := s.db.ExecContext(ctx,
		`INSERT INTO users (id, username, password_hash, role, created_at)
		 VALUES (?, ?, ?, ?, ?)`,
		u.ID, u.Username, u.PasswordHash, string(u.Role), u.CreatedAt.UTC().Format(time.RFC3339Nano))
	if err != nil {
		return fmt.Errorf("store: create user: %w", err)
	}
	return nil
}

// GetUserByUsername looks up a user by their (case-sensitive) username.
// Returns ErrNotFound if no row matches.
func (s *Store) GetUserByUsername(ctx context.Context, username string) (*User, error) {
	row := s.db.QueryRowContext(ctx,
		`SELECT id, username, password_hash, role, created_at, last_login_at
		 FROM users WHERE username = ?`, username)
	return scanUser(row)
}

// GetUserByID looks up a user by id. Returns ErrNotFound on miss.
func (s *Store) GetUserByID(ctx context.Context, id string) (*User, error) {
	row := s.db.QueryRowContext(ctx,
		`SELECT id, username, password_hash, role, created_at, last_login_at
		 FROM users WHERE id = ?`, id)
	return scanUser(row)
}

// CountUsers returns the total number of user rows. The first-run
// bootstrap uses this to detect a fresh install.
func (s *Store) CountUsers(ctx context.Context) (int, error) {
	var n int
	if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&n); err != nil {
		return 0, fmt.Errorf("store: count users: %w", err)
	}
	return n, nil
}

// MarkUserLogin records a successful authentication.
func (s *Store) MarkUserLogin(ctx context.Context, id string, when time.Time) error {
	_, err := s.db.ExecContext(ctx,
		`UPDATE users SET last_login_at = ? WHERE id = ?`,
		when.UTC().Format(time.RFC3339Nano), id)
	if err != nil {
		return fmt.Errorf("store: mark login: %w", err)
	}
	return nil
}

func scanUser(row *sql.Row) (*User, error) {
	var u User
	var role string
	var lastLogin sql.NullString
	var created string
	if err := row.Scan(&u.ID, &u.Username, &u.PasswordHash, &role, &created, &lastLogin); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrNotFound
		}
		return nil, fmt.Errorf("store: scan user: %w", err)
	}
	u.Role = Role(role)
	t, err := time.Parse(time.RFC3339Nano, created)
	if err != nil {
		return nil, fmt.Errorf("store: parse created_at: %w", err)
	}
	u.CreatedAt = t
	if lastLogin.Valid {
		t, err := time.Parse(time.RFC3339Nano, lastLogin.String)
		if err != nil {
			return nil, fmt.Errorf("store: parse last_login_at: %w", err)
		}
		u.LastLoginAt = &t
	}
	return &u, nil
}
