package datamysql

import (
	"database/sql"
	"fmt"

	"bitbucket.org/nemt/nemt-portal-api/domain/entity"
	"bitbucket.org/nemt/nemt-portal-api/infra/errors"
	uuid "github.com/satori/go.uuid"
)

// rideRepo maps methods to database
type passwordResetRepo struct {
	conn executor
}

func newPasswordResetRepo(conn executor) *passwordResetRepo {
	return &passwordResetRepo{
		conn: conn,
	}
}

func (c *passwordResetRepo) getQuery() string {
	const (
		query = `SELECT 
										a.password_reset_id,
										a.password_reset_uuid,
										a.user_id,
											b.user_uuid,
										a.token,
										a.create_date,
										a.expire_date,
										(IFNULL(a.used, b'0') = b'1') used,
										(IFNULL(a.opened, b'0') = b'1') opened
							FROM
										tab_password_reset 	a
							INNER JOIN 	tab_user		 	b 
								ON 		a.user_id	= 	b.user_id`
	)

	return query
}

// parseSet parses a result set result to an entity array
func (c *passwordResetRepo) parseSet(rows *sql.Rows, err error) ([]entity.PasswordReset, error) {
	if err != nil {
		return nil, errors.Wrap(err)
	}
	result := make([]entity.PasswordReset, 0)
	for rows.Next() {
		entity, err := c.parseEntity(rows)
		if err != nil {
			return nil, errors.Wrap(err)
		}
		result = append(result, entity)
	}
	return result, nil
}

// parseEntity parses a result to an entity
func (c *passwordResetRepo) parseEntity(row scanner) (retVal entity.PasswordReset, err error) {
	err = row.Scan(
		&retVal.ID, &retVal.UUID, &retVal.User.ID, &retVal.User.UUID, &retVal.Token, &retVal.Created, &retVal.Expires, &retVal.Used, &retVal.Opened)

	return retVal, errors.Wrap(err)
}

func (c *passwordResetRepo) GetAll() ([]entity.PasswordReset, error) {
	return c.parseSet(c.conn.Query(c.getQuery()))
}

func (c *passwordResetRepo) CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) {
	const (
		insertQuery = `INSERT INTO tab_password_reset(password_reset_uuid, user_id, token, expire_date, used, opened) VALUES(?, ?, ?, ?, 0, 0);`
	)

	retVal := passwordResetEntry
	guid, _ := uuid.NewV4()

	userRepo := newUserRepo(c.conn)

	fullUser, err := userRepo.GetByEmail(passwordResetEntry.User.Email)
	if err != nil {
		return retVal, err
	}

	if fullUser.Email != passwordResetEntry.User.Email {
		return retVal, fmt.Errorf("User not found")
	}

	results, err := c.conn.Exec(insertQuery, guid, fullUser.ID, passwordResetEntry.Token, passwordResetEntry.Expires)
	if err != nil {
		return retVal, err
	}

	retVal.ID, err = results.LastInsertId()
	if err != nil {
		return retVal, err
	}

	return c.GetByID(retVal.ID)
}

func (c *passwordResetRepo) GetByID(ID int64) (entity.PasswordReset, error) {
	return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE  a.password_reset_id = ?; ", ID))
}

func (c *passwordResetRepo) GetByToken(token string) (entity.PasswordReset, error) {
	return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE  a.token = ? AND a.used = 0; ", token))
}

func (c *passwordResetRepo) SetTokenOpened(token string) error {
	const (
		query = `UPDATE		tab_password_reset   a
				 SET		a.opened	=   1
				 WHERE  	a.token     = ? AND a.used = 0 AND a.expire_date > CURRENT_TIMESTAMP`
	)

	result, err := c.conn.Exec(query, token)
	if err != nil {
		return err
	}

	if updateCount, err := result.RowsAffected(); err != nil || updateCount == 0 {
		return fmt.Errorf("Invalid token")
	}

	return nil
}

func (c *passwordResetRepo) SetTokenUsed(token string) error {
	const (
		query = `UPDATE		tab_password_reset   a
				 SET		a.opened	=   1,
				 			a.used		=	1
				 WHERE  	a.token     = ? AND a.used = 0`
	)

	if _, err := c.conn.Exec(query, token); err != nil {
		return err
	}

	return nil
}
