package main

/*
CREATE TABLE user (
	id TEXT NOT NULL PRIMARY KEY,
	email_address TEXT NOT NULL UNIQUE,
	password_hash BLOB NOT NULL,
	password_salt BLOB NOT NULL,
	password_hash_algorithm_id TEXT NOT NULL,
	disabled INTEGER NOT NULL DEFAULT 0,
	email_address_counter INTEGER NOT NULL DEFAULT 0,
	password_hash_counter INTEGER NOT NULL DEFAULT 0,
	disabled_counter INTEGER NOT NULL DEFAULT 0,
	sessions_counter INTEGER NOT NULL DEFAULT 0
) STRICT;
*/

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

	"github.com/faroedev/faroe"
)

type userStoreStruct struct {
	db *sql.DB
}

func (userStore *userStoreStruct) CreateUser(emailAddress string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte) (faroe.UserStruct, error) {
	// TODO
	id := generateRandomId()

	_, err := userStore.db.Exec("INSERT INTO user (id, email_address, password_hash, password_hash_algorithm_id, password_salt) VALUES (?, ?, ?, ?, ?)", id, emailAddress, passwordHash, passwordHashAlgorithmId, passwordSalt)

	if err != nil {
		// TODO
		if isUniqueConstraintError(err) {
			return faroe.UserStruct{}, faroe.ErrUserStoreUserEmailAddressAlreadyUsed
		}
		return faroe.UserStruct{}, fmt.Errorf("failed to run query: %s", err.Error())
	}

	user := faroe.UserStruct{
		Id:                      id,
		EmailAddress:            emailAddress,
		PasswordHash:            passwordHash,
		PasswordHashAlgorithmId: passwordHashAlgorithmId,
		PasswordSalt:            passwordSalt,
		DisplayName:             "",
		Disabled:                false,
		EmailAddressCounter:     0,
		PasswordHashCounter:     0,
		DisabledCounter:         0,
		SessionsCounter:         0,
	}
	return user, nil
}

func (userStore *userStoreStruct) GetUser(userId string) (faroe.UserStruct, error) {
	row := userStore.db.QueryRow("SELECT email_address, password_hash, password_hash_algorithm_id, password_salt, disabled, email_address_counter, password_hash_counter, disabled_counter, sessions_counter FROM user WHERE id = ?", userId)

	var user faroe.UserStruct
	user.Id = userId
	err := row.Scan(
		&user.EmailAddress,
		&user.PasswordHash,
		&user.PasswordHashAlgorithmId,
		&user.PasswordSalt,
		&user.Disabled,
		&user.EmailAddressCounter,
		&user.PasswordHashCounter,
		&user.DisabledCounter,
		&user.SessionsCounter,
	)
	if err != nil && errors.Is(err, sql.ErrNoRows) {
		return faroe.UserStruct{}, faroe.ErrUserStoreUserNotFound
	}
	if err != nil {
		return faroe.UserStruct{}, fmt.Errorf("failed to run query: %s", err.Error())
	}
	return user, nil
}

func (userStore *userStoreStruct) GetUserByEmailAddress(emailAddress string) (faroe.UserStruct, error) {
	row := userStore.db.QueryRow("SELECT id, password_hash, password_hash_algorithm_id, password_salt, disabled, email_address_counter, password_hash_counter, disabled_counter, sessions_counter FROM user WHERE email_address = ?", emailAddress)

	var user faroe.UserStruct
	user.EmailAddress = emailAddress
	err := row.Scan(
		&user.Id,
		&user.PasswordHash,
		&user.PasswordHashAlgorithmId,
		&user.PasswordSalt,
		&user.Disabled,
		&user.EmailAddressCounter,
		&user.PasswordHashCounter,
		&user.DisabledCounter,
		&user.SessionsCounter,
	)
	if err != nil && errors.Is(err, sql.ErrNoRows) {
		return faroe.UserStruct{}, faroe.ErrUserStoreUserNotFound
	}
	if err != nil {
		return faroe.UserStruct{}, fmt.Errorf("failed to run query: %s", err.Error())
	}

	return user, nil
}

func (userStore *userStoreStruct) UpdateUserEmailAddress(userId string, emailAddress string, userEmailAddressCounter int32) error {
	result, err := userStore.db.Exec("UPDATE user SET email_address = ?, email_address_counter = email_address_counter + 1 WHERE id = ? AND email_address_counter = ?", emailAddress, userId, userEmailAddressCounter)

	if err != nil {
		// TODO
		if isUniqueConstraintError(err) {
			return faroe.ErrUserStoreUserEmailAddressAlreadyUsed
		}
		return fmt.Errorf("failed to run query: %s", err.Error())
	}

	rowsAffected, _ := result.RowsAffected()
	if rowsAffected < 1 {
		return faroe.ErrUserStoreUserNotFound
	}

	return nil
}

func (userStore *userStoreStruct) UpdateUserPasswordHash(userId string, passwordHash []byte, passwordHashAlgorithmId string, passwordSalt []byte, userPasswordHashCounter int32) error {
	result, err := userStore.db.Exec("UPDATE user SET password_hash = ?, password_hash_algorithm_id = ?, password_salt = ?, password_hash_counter = password_hash_counter + 1 WHERE id = ? AND password_hash_counter = ?", passwordHash, passwordHashAlgorithmId, passwordSalt, userId, userPasswordHashCounter)

	if err != nil {
		return fmt.Errorf("failed to run query: %s", err.Error())
	}

	rowsAffected, _ := result.RowsAffected()
	if rowsAffected < 1 {
		return faroe.ErrUserStoreUserNotFound
	}

	return nil
}

func (userStore *userStoreStruct) IncrementUserSessionsCounter(userId string, userSessionsCounter int32) error {
	result, err := userStore.db.Exec("UPDATE user SET sessions_counter = sessions_counter + 1 WHERE id = ? AND sessions_counter = ?", userId, userSessionsCounter)

	if err != nil {
		return fmt.Errorf("failed to run query: %s", err.Error())
	}

	rowsAffected, _ := result.RowsAffected()
	if rowsAffected < 1 {
		return faroe.ErrUserStoreUserNotFound
	}

	return nil
}

func (userStore *userStoreStruct) DeleteUser(userId string) error {
	result, err := userStore.db.Exec("DELETE FROM user WHERE id = ?", userId)
	if err != nil {
		return fmt.Errorf("failed to run query: %s", err.Error())
	}

	rowsAffected, _ := result.RowsAffected()
	if rowsAffected < 1 {
		return faroe.ErrUserStoreUserNotFound
	}

	return nil
}