package repository

import (
	"database/sql"
	"fmt"

	domain "github.com/haydenhargreaves/Potion/internal/domain/user"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
)

type UserRepository struct {
	db *sqlx.DB
}

// Compile-time check to ensure the UserRepository implements domain.UserRepository
var _ domain.UserRepository = (*UserRepository)(nil)

// NewUserRepository creates a user repository object which is used by the user service to access
// the database. Any user related database operations will take place in this repository.
func NewUserRepository(db *sqlx.DB) domain.UserRepository {
	return &UserRepository{db: db}
}

// CreateGoogleUser creates a user entry in the users table. The refresh token is required along
// with the Google user info, in order to complete the database schema. Currently, Google login is
// the only support method of authentication, if this changes, this repository may need updates to
// match an updated table schema.
//
// This function will NOT check if the user already exists, if they do, it will return an error. For
// best results, pair this function with the GetGoogleUser which will return the user if it can find
// it.
func (r *UserRepository) CreateGoogleUser(googleUserInfo *domain.GoogleUserInfo, googleRefreshToken string) (domain.User, error) {
	tx, err := r.db.Begin()
	if err != nil {
		return domain.User{}, err
	}

	if googleUserInfo == nil {
		return domain.User{}, fmt.Errorf("Google user info provided was nil")
	}

	var user domain.User
	query := `INSERT INTO users 
	(GoogleId, Name, Email, ImageUrl, GoogleRefreshToken) 
	VALUES ($1, $2, $3, $4, $5) RETURNING *;`

	if err := tx.QueryRow(
		query,
		googleUserInfo.Id,
		googleUserInfo.Name,
		googleUserInfo.Email,
		googleUserInfo.Picture,
		googleRefreshToken,
	).Scan(
		&user.Id,
		&user.GoogleId,
		&user.Name,
		&user.Email,
		&user.ImageUrl,
		&user.GoogleRefreshToken,
		&user.Created,
	); err != nil {
		tx.Rollback()
		return domain.User{}, err
	}

	if err := tx.Commit(); err != nil {
		return domain.User{}, err
	}

	return user, nil
}

// GetGoogleUser attempts to find a user in the database via its Google ID, not the database ID. This
// function is used when a user logs in with Google to prevent duplicate entries from being made. If
// no user is found, this function will return a null pointer but not an error.
func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
	var user domain.User
	query := `SELECT * FROM users WHERE GoogleId = $1`

	if err := r.db.QueryRow(query, googleId).Scan(
		&user.Id,
		&user.GoogleId,
		&user.Name,
		&user.Email,
		&user.ImageUrl,
		&user.GoogleRefreshToken,
		&user.Created,
	); err != nil {
		// If no user was found, don't error, just return
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return &user, nil
}

// GetUser gets a user from the database via its ID. The operation is wrapped in a transaction
// for added safety. The repository will not check for a nil result, instead the service will.
// Callers are responsible for protecting against double nil results. Any errors will be bubbled
// to the caller.
func (r *UserRepository) GetUser(id int) (*domain.User, error) {
	query := "SELECT * FROM users WHERE id = $1"

	var user domain.User
	if err := r.db.QueryRow(query, id).Scan(
		&user.Id,
		&user.GoogleId,
		&user.Name,
		&user.Email,
		&user.ImageUrl,
		&user.GoogleRefreshToken,
		&user.Created,
	); err != nil {
		// If no user was found, don't error, just return
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	return &user, nil
}
