package datamysql

import (
	"database/sql"
	"fmt"
	"time"

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

// userRepo maps methods to database
type userRepo struct {
	conn executor
}

func newUserRepo(conn executor) *userRepo {
	return &userRepo{
		conn: conn,
	}
}

func (c *userRepo) GetByMemberID(memberID string) (entity.User, error) {
	finalQuery := c.getQuery() + " AND a.subscriber_id = ? AND e.key = 'US'"

	user, err := c.parseSet(c.conn.Query(finalQuery, memberID))
	if err != nil {
		return entity.User{}, err
	}

	if len(user) > 0 {
		retVal := user[0]
		retVal.Contacts, err = c.GetContacts(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}

		retVal.Addresses = nil
		retVal.Addresses, err = c.getAddressByUserID(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}
		return retVal, nil
	} else {
		return entity.User{}, nil
	}
}

func (c *userRepo) GetByEmail(email string) (entity.User, error) {
	finalQuery := c.getQuery() + " AND b.email = ?"

	user, err := c.parseSet(c.conn.Query(finalQuery, email))
	if err != nil {
		return entity.User{}, err
	}

	if len(user) > 0 {
		retVal := user[0]
		retVal.Contacts, err = c.GetContacts(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}

		retVal.Addresses = nil
		retVal.Addresses, err = c.getAddressByUserID(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}
		return retVal, nil
	} else {
		return entity.User{}, nil
	}
}

func (c *userRepo) GetByUUID(uuid string, profile string) (entity.User, error) {
	params := make([]interface{}, 0)
	params = append(params, uuid)
	finalQuery := c.getQuery() + " AND a.user_uuid = ? "
	if profile == "US" {
		finalQuery += " AND e.key = ? "
		params = append(params, profile)
	}

	user, err := c.parseSet(c.conn.Query(finalQuery, params...))
	if err != nil {
		return entity.User{}, err
	}

	if len(user) > 0 {
		retVal := user[0]
		retVal.Contacts, err = c.GetContacts(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}

		retVal.Addresses = nil
		retVal.Addresses, err = c.getAddressByUserID(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}
		return retVal, nil
	} else {
		return entity.User{}, nil
	}
}

func (c *userRepo) GetContacts(userID int64) ([]entity.ContactInfo, error) {
	const (
		query = `
		SELECT 
								a.contact_id, 
								a.contact, 
								a.user_id,
								b.contact_type_id, 
								b.key               contact_type_key, 
								b.name              contact_type_name,
								a.contact_uuid 
			FROM      tab_contact         a 
		INNER JOIN tab_contact_type    b 
				ON      a.contact_type_id = b.contact_type_id 
		WHERE      a.user_id         = ?;
		`
	)

	rows, err := c.conn.Query(query, userID)
	if err != nil {
		return nil, err
	}

	retVal := make([]entity.ContactInfo, 0)
	for rows.Next() {
		contact := entity.ContactInfo{}
		err = rows.Scan(&contact.ID, &contact.Value, &contact.User.ID, &contact.Type.ID, &contact.Type.Key, &contact.Type.Value, &contact.UUID)
		if err != nil {
			return nil, err
		}
		retVal = append(retVal, contact)
	}

	return retVal, nil
}

func (c *userRepo) GetUsersByProfile(profile string) ([]entity.User, error) {
	query := c.getQuery()
	if profile == "" {
		query += "AND e.key <> 'US' "
		return c.parseSet(c.conn.Query(query))
	} else {
		return c.parseSet(c.conn.Query(query+"AND e.key = ? AND a.active = 1 ", profile))
	}
}

func (c *userRepo) parseSet(rows *sql.Rows, err error) ([]entity.User, error) {
	if err != nil {
		return nil, errors.Wrap(err)
	}
	result := make([]entity.User, 0)
	var lastUser entity.User
	for rows.Next() {
		entity, err := c.parseEntity(rows)
		if err != nil {
			return nil, errors.Wrap(err)
		}

		if entity.UUID == lastUser.UUID {
			lastUser.Profiles = append(lastUser.Profiles, entity.Profiles...)
		} else {
			if lastUser.UUID != "" {
				result = append(result, lastUser)
			}
			lastUser = entity
		}
	}
	result = append(result, lastUser)

	return result, nil
}

func (c *userRepo) GetContactType() (retVal []entity.ContactType, err error) {
	const (
		query = `
				SELECT 
								a.contact_type_id,
								a.key,
								a.name
					FROM tab_contact_type a
					WHERE a.visible = 1 `
	)

	rows, err := c.conn.Query(query)
	if err != nil {
		return nil, err
	}

	retVal = make([]entity.ContactType, 0)
	for rows.Next() {
		var contactType entity.ContactType
		err = rows.Scan(&contactType.ID, &contactType.Key, &contactType.Value)
		if err != nil {
			return nil, err
		}

		retVal = append(retVal, contactType)
	}

	return retVal, nil
}

// parseEntity parses a result to an entity
func (c *userRepo) parseEntity(row scanner) (retVal entity.User, err error) {
	var profile entity.Profile
	var birthDate mysql.NullTime

	var homeAddress entity.Address
	var workAddress entity.Address

	err = row.Scan(&retVal.ID, &retVal.UUID, &retVal.Name, &retVal.Member, &birthDate, &retVal.LoginID, &retVal.LoginUUID, &retVal.Email, &retVal.PhoneNumber, &retVal.LoginKey, &retVal.Gender, &retVal.Active, &retVal.Created, &retVal.Updated, &profile.ID, &profile.Name, &profile.Key, &profile.Active, &profile.Blocked, &profile.Suspended, &profile.Created, &profile.Updated, &profile.Organization.ID, &profile.Organization.UUID, &profile.Organization.Type.ID, &profile.Organization.Type.Name, &profile.Organization.Type.Key, &profile.Organization.Type.Description, &profile.Organization.Name, &profile.Organization.Description, &profile.Organization.ReferenceID, &profile.Organization.ParentID, &profile.Organization.Main,
		&homeAddress.ID, &homeAddress.UUID, &homeAddress.AddressType.ID, &homeAddress.AddressType.Key, &homeAddress.AddressType.Name, &homeAddress.Name, &homeAddress.Address, &homeAddress.Latitude, &homeAddress.Longitude,
		&workAddress.ID, &workAddress.UUID, &workAddress.AddressType.ID, &workAddress.AddressType.Key, &workAddress.AddressType.Name, &workAddress.Name, &workAddress.Address, &workAddress.Latitude, &workAddress.Longitude, &retVal.Type, &retVal.Test)
	if err != nil {
		if err != sql.ErrNoRows {
			return retVal, errors.Wrap(err)
		}
	}

	if birthDate.Valid {
		retVal.BirthDate = birthDate.Time
	}

	retVal.Profiles = append(retVal.Profiles, profile)
	if homeAddress.ID > 0 {
		retVal.Addresses = append(retVal.Addresses, homeAddress)
	}

	if workAddress.ID > 0 {
		retVal.Addresses = append(retVal.Addresses, workAddress)
	}

	return retVal, errors.Wrap(err)
}

func (c *userRepo) FullLogin(loginType string, key string, pass string, profile string) (user entity.User, err error) {
	return c.login(loginType, key, pass, profile)
}

//Login return a user based on email and password with the default `User` profile
func (c *userRepo) Login(email string, pass string) (user entity.User, err error) {
	return c.FullLogin("email", email, pass, "US")
}

func (c *userRepo) getQuery() string {
	const (
		query = `SELECT 
								a.user_id,
								a.user_uuid,
								a.name,
								IFNULL(a.subscriber_id, '') subscriber_id,
								a.birth_date,
								b.login_id,
								b.login_uuid,
								IFNULL(b.email, '') email,
								IFNULL(b.phone_number, '') phone_number,
								b.key login_key,
								IFNULL(a.gender, 'U') gender,
								(IFNULL(a.active, b'0') = b'1') active,
								a.create_time,
								a.update_time,
								e.profile_id,
								e.name profile_name,
								e.key profile_key,
								(IFNULL(c.active, b'0') = b'1') profile_active, 
								(IFNULL(c.blocked, b'0') = b'1') profile_blocked,
								(IFNULL(c.suspended, b'0') = b'1') profile_suspended,
								c.create_date profile_createat,
								c.update_date profile_updateat,
								IFNULL(f.organization_id, 0) organization_id,
								IFNULL(f.organization_uuid, '') organization_uuid,
								IFNULL(f.organization_type_id, 0) organization_type_id,
								IFNULL(g.organization_type, '') organization_type,
								IFNULL(g.organization_type_key, '') organization_type_key,
								IFNULL(g.description, '') organization_type_desc,
								IFNULL(f.organization_name, '') organization_name,
								IFNULL(f.organization_description, '') organization_desc,
								IFNULL(f.organization_reference_id, 0) organization_reference_id,
								IFNULL(f.organization_parent_id, 0) organization_parent_id,
								(IFNULL(f.main_organization, b'0') = b'1') main_organization,
								IFNULL(h.address_id, 0) home_address_id,
								IFNULL(h.address_uuid, '') home_address_uuid,
								IFNULL(h.address_type_id, 0) home_address_type_id,
								IFNULL(h.address_type_key, '') home_address_type_key,
								IFNULL(h.address_type_name, '') home_address_type_name,
								IFNULL(h.name, '') home_name,
								IFNULL(h.address, '') home_address,
								IFNULL(h.lat, 0) home_lat,
								IFNULL(h.long, 0) home_long,
								IFNULL(i.address_id, 0) work_address_id,
								IFNULL(i.address_uuid, '') work_address_uuid,
								IFNULL(i.address_type_id, 0) work_address_type_id,
								IFNULL(i.address_type_key, '') work_address_type_key,
								IFNULL(i.address_type_name, '') work_address_type_name,
								IFNULL(i.name, '') work_name,
								IFNULL(i.address, '') work_address,
								IFNULL(i.lat, 0) work_lat,
								IFNULL(i.long, 0) work_long,
								IFNULL(a.member_type, 'S') member_type,
								user_test
						FROM
								tab_user a
										INNER JOIN
								tab_login b ON a.user_id = b.user_id
										INNER JOIN
								tab_profile_login c ON c.login_id = b.login_id
										INNER JOIN
								tab_profile e ON e.profile_id = c.profile_id
										LEFT JOIN
								tab_organization f ON f.organization_id = IFNULL(c.organization_id, 0)
										LEFT JOIN
								tab_organization_type g ON IFNULL(g.organization_type_id, 0) = IFNULL(f.organization_type_id, 0)
                                        LEFT JOIN
                                viw_address_home h ON a.user_id = h.user_id
                                        LEFT JOIN
                                viw_address_work i ON a.user_id = i.user_id
						WHERE
								a.active = b.active
										AND b.active = c.active
										AND c.active = e.active `
	)

	return query
}

func (c *userRepo) login(loginType string, key string, pass string, profile string) (user entity.User, err error) {
	if loginType == "" {
		return user, errors.NewNullArgumentError("loginType")
	}

	if loginType != "phone_number" && loginType != "email" {
		return user, errors.NewValidationError("loginType", "invalid")
	}

	query := fmt.Sprintf(c.getQuery()+`
										AND a.active = 1
										AND b.%s = ?
										AND b.password = sha2(?, 512) `, loginType)

	users, err := c.parseSet(c.conn.Query(query, key, pass))
	if err != nil {
		return user, errors.Wrap(err)
	}

	return users[0], nil
}

func (c *userRepo) Create(user entity.User) (retVal entity.User, err error) {
	retVal = user

	if retVal.Member != "" {
		retVal, err = c.getUserByMember(retVal)
		if err != nil {
			return retVal, err
		}
	}

	if retVal.ID == 0 || retVal.Member == "" {
		userID, guid, err := c.createUser(user)
		if err != nil {
			return retVal, err
		}
		retVal.UUID = guid
		retVal.ID = userID
	}

	retVal, err = c.getUserLogin(retVal)
	if err != nil {
		return retVal, err
	}

	if retVal.LoginID == 0 {
		loginID, loginGUID, err := c.createLogin(retVal)
		if err != nil {
			return retVal, err
		}
		retVal.LoginUUID = loginGUID
		retVal.LoginID = loginID
	}

	for _, p := range user.Profiles {
		companyProfileID, err := c.getCompanyProfile(p.Key)
		if err != nil {
			return retVal, err
		}

		if len(user.Organizations) > 0 {
			for _, o := range user.Organizations {
				err = c.addProfileToUser(retVal.LoginID, companyProfileID, o)
				if err != nil {
					return retVal, err
				}
			}
		} else {
			err = c.addProfileToUser(retVal.LoginID, companyProfileID, entity.Organization{})
			if err != nil {
				return retVal, err
			}
		}
	}

	if retVal.Email != "" {
		contact := entity.ContactInfo{
			Type:  entity.ContactType{Key: "email"},
			Value: retVal.Email,
			User: entity.User{
				ID: retVal.ID,
			},
		}

		contact, err = c.addContactInfo(contact)
		if err != nil {
			return retVal, err
		}
	}

	if retVal.PhoneNumber != "" {
		contact := entity.ContactInfo{
			Type:  entity.ContactType{Key: "phone"},
			Value: retVal.PhoneNumber,
			User: entity.User{
				ID: retVal.ID,
			},
		}

		contact, err = c.addContactInfo(contact)
		if err != nil {
			return retVal, err
		}
	}

	return c.GetByUUID(retVal.UUID, "")
}

func (c *userRepo) SaveContact(contact entity.ContactInfo) (entity.ContactInfo, error) {
	return c.addContactInfo(contact)
}

func (c *userRepo) UpdateLogin(user entity.User) error {
	const (
		query = `UPDATE      tab_login   a
							INNER JOIN tab_user    b
									ON      a.user_id       =   b.user_id
								SET      a.email         =   ?,
													a.phone_number  =   ?
							WHERE  
													b.user_uuid     = ?`
	)

	if _, err := c.conn.Exec(query, user.Email, user.PhoneNumber, user.UUID); err != nil {
		return err
	}

	return nil
}

func (c *userRepo) RemoveContact(contact entity.ContactInfo) (entity.ContactInfo, error) {
	const (
		query = `DELETE FROM tab_contact WHERE contact_uuid = ?;`
	)

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

	retVal := contact
	retVal.ID, err = result.LastInsertId()
	if err != nil {
		return contact, err
	}

	return retVal, nil
}

func (c *userRepo) addContactInfo(contact entity.ContactInfo) (entity.ContactInfo, error) {
	const (
		query = `INSERT INTO tab_contact(contact_type_id, user_id, contact, contact_uuid)
							SELECT a.contact_type_id, ? user_id, ? contact, ? contact_uuid
							FROM 
										tab_contact_type a 
							LEFT JOIN 	tab_contact b 
								ON 		a.contact_type_id = b.contact_type_id 
							AND 		b.user_id = ? 
							AND 		b.contact = ? 
							WHERE 		a.key = ?
							AND       b.contact_id IS NULL;`
	)

	sUUID, err := uuid.NewV4()
	if err != nil {
		return contact, err
	}

	contact.UUID = sUUID.String()
	result, err := c.conn.Exec(query, contact.User.ID, contact.Value, contact.UUID, contact.User.ID, contact.Value, contact.Type.Key)
	if err != nil {
		return contact, err
	}

	retVal := contact
	retVal.ID, err = result.LastInsertId()
	if err != nil {
		return contact, err
	}

	return retVal, nil
}

func (c *userRepo) getUserByLoginAndProfile(loginID int64) (user entity.User, err error) {
	finalQuery := c.getQuery() + " AND a.active = 1 AND b.login_Id = ? "

	users, err := c.parseSet(c.conn.Query(finalQuery, loginID))
	if err != nil {
		return user, errors.Wrap(err)
	}

	return users[0], nil
}

func (c *userRepo) addProfileToUser(loginID int64, profileID int64, organization entity.Organization) error {
	const query = "INSERT INTO tab_profile_login(profile_id, login_id, active, blocked, suspended, organization_id) VALUES(?, ?, 1, 0, 0, ?) ON DUPLICATE KEY UPDATE profile_id = ?, login_id = ?, organization_id = ?;"
	const checkQuery = "SELECT COUNT(1) has_profile FROM tab_profile_login a WHERE a.login_id = ? AND a.profile_id = ? AND IFNULL(a.organization_id, 0) = ?;"

	var total int64
	row := c.conn.QueryRow(checkQuery, loginID, profileID, organization.ID)
	err := row.Scan(&total)
	if err != nil && err != sql.ErrNoRows {
		return err
	}

	if total < 1 {
		_, err := c.conn.Exec(query, profileID, loginID, organization.ID, profileID, loginID, toNullInt64(organization.ID))
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *userRepo) getUserByMember(u entity.User) (user entity.User, err error) {
	user = u
	users, err := c.parseSet(c.conn.Query(c.getQuery()+" AND a.active = 1 AND e.key = 'US' AND IFNULL(a.subscriber_id, '') = ?;", u.Member))
	if err != nil {
		return user, errors.Wrap(err)
	}

	if users[0].ID != 0 {
		user = users[0]
	}
	return user, nil
}

func (c *userRepo) getCompanyProfile(profile string) (int64, error) {
	const (
		query = "SELECT a.profile_id FROM tab_profile a WHERE a.key = ?;"
	)
	var companyProfileID int64
	row := c.conn.QueryRow(query, profile)
	err := row.Scan(&companyProfileID)
	if err != nil {
		return 0, err
	}

	return companyProfileID, nil
}

func (c *userRepo) getUserLogin(u entity.User) (user entity.User, err error) {
	const (
		query = "SELECT login_id, login_uuid, user_id, IFNULL(phone_number, '') phone_number, IFNULL(email, '') email, `key`, `password` FROM tab_login WHERE user_id = ?"
	)

	row := c.conn.QueryRow(query, user.Member)
	err = row.Scan(&user.LoginID, &user.LoginUUID, &user.ID, &user.PhoneNumber, &user.Email, &user.LoginKey, &user.Pass)
	if err != nil {
		if err == sql.ErrNoRows {
			return u, nil
		} else {
			return user, err
		}
	}

	return user, nil
}

func (c *userRepo) createLogin(user entity.User) (int64, string, error) {
	const (
		query = "INSERT INTO tab_login (login_uuid, user_id, phone_number, email, `key`, `password`) VALUES(?, ?, ?, ?, ?, sha2(?, 512)) ON DUPLICATE KEY UPDATE login_uuid = ?, user_id = ?, phone_number = ?, email = ?, `key` = ?, `password` = sha2(?, 512);"
	)
	guid, _ := uuid.NewV4()
	key, _ := uuid.NewV4()

	result, err := c.conn.Exec(query, guid.String(), user.ID, toNullString(user.PhoneNumber), toNullString(user.Email), key.String(), user.Pass, guid.String(), user.ID, user.PhoneNumber, user.Email, key.String(), user.Pass)
	if err != nil {
		return 0, "", err
	}

	loginID, err := result.LastInsertId()
	if err != nil {
		return 0, "", err
	}

	return loginID, guid.String(), nil
}

func (c *userRepo) createUser(user entity.User) (int64, string, error) {
	const (
		query = "INSERT INTO tab_user(user_uuid, `name`, subscriber_id, birth_date, gender, member_type, user_hash, user_test) VALUES(?, ?, ?, ?, ?, ?, SHA2(CONCAT_WS('-',?,?,?,?,?), 512), ?) ON DUPLICATE KEY UPDATE user_uuid = ?, `name` = ?, subscriber_id = ?, birth_date = ?, gender = ?, user_test = ?;"
	)
	guid, _ := uuid.NewV4()

	result, err := c.conn.Exec(query, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Test, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Test)
	if err != nil {
		return 0, "", err
	}

	userID, err := result.LastInsertId()
	if err != nil {
		return 0, "", err
	}

	return userID, guid.String(), nil
}

func (c *userRepo) RemoveAddress(addressUUID string) error {
	const (
		query = "UPDATE tab_address SET active = 0 WHERE address_uuid = ?;"
	)

	_, err := c.conn.Exec(query, addressUUID)
	return err
}

func (c *userRepo) SaveAddress(address entity.Address) (entity.Address, error) {
	const (
		query = "INSERT INTO tab_address(address_uuid,address_type_id,address_origin_id,user_id,internal_id,`name`,address,lat,`long`,created_user_id) VALUES (?,?,?,?,?,?,?,?,?,?);"
	)

	address, err := c.getAddressSecondaryData(address)
	if err != nil {
		return address, err
	}

	uuid, _ := uuid.NewV4()
	address.UUID = uuid.String()

	result, err := c.conn.Exec(query, address.UUID, address.AddressType.ID, address.Origin.ID, address.User.ID, address.InternalID, address.Name, address.Address, address.Latitude, address.Longitude, address.CreatedUser.ID)
	if err != nil {
		return address, err
	}

	addressID, err := result.LastInsertId()
	if err != nil {
		return address, err
	}

	return c.getAddressByID(addressID)
}

func (c *userRepo) getAddressByUserID(userID int64) ([]entity.Address, error) {
	const (
		query = `SELECT 
									a.address_id,
									a.address_uuid,
									a.address_type_id,
									d.key address_type_key,
									d.name address_type_name,
									a.address_origin_id,
									e.key address_origin_key,
									e.name address_origin_name,
									b.user_id,
									b.user_uuid,
									b.name user_name,
									a.internal_id,
									a.name,
									a.address,
									a.lat,
									a.long,
									c.user_id created_user_id,
									c.user_uuid created_user_uuid,
									c.name created_user_name
						FROM
													tab_address a
					INNER JOIN     tab_user b
							ON          a.user_id = b.user_id
					INNER JOIN     tab_user c
							ON          a.created_user_id = c.user_id
					INNER JOIN     tab_address_type d
							ON          a.address_type_id = d.address_type_id
					INNER JOIN     tab_address_origin e
							ON          a.address_origin_id = e.address_origin_id
					WHERE
									a.user_id = ? and a.active = 1;`
	)

	var addresses []entity.Address
	rows, err := c.conn.Query(query, userID)
	if err != nil {
		return nil, err
	}

	for rows.Next() {
		var address entity.Address
		err := rows.Scan(&address.ID, &address.UUID, &address.AddressType.ID, &address.AddressType.Key, &address.AddressType.Name,
			&address.Origin.ID, &address.Origin.Key, &address.Origin.Name, &address.User.ID, &address.User.UUID, &address.User.Name,
			&address.InternalID, &address.Name, &address.Address, &address.Latitude, &address.Longitude, &address.CreatedUser.ID,
			&address.CreatedUser.UUID, &address.CreatedUser.Name)
		if err != nil {
			return nil, err
		}
		addresses = append(addresses, address)
	}
	return addresses, nil
}

func (c *userRepo) getAddressByID(addressID int64) (entity.Address, error) {
	const (
		query = `SELECT 
									a.address_id,
									a.address_uuid,
									a.address_type_id,
									d.key address_type_key,
									d.name address_type_name,
									a.address_origin_id,
									e.key address_origin_key,
									e.name address_origin_name,
									b.user_id,
									b.user_uuid,
									b.name user_name,
									a.internal_id,
									a.name,
									a.address,
									a.lat,
									a.long,
									c.user_id created_user_id,
									c.user_uuid created_user_uuid,
									c.name created_user_name
						FROM
													tab_address a
					INNER JOIN     tab_user b
							ON          a.user_id = b.user_id
					INNER JOIN     tab_user c
							ON          a.created_user_id = c.user_id
					INNER JOIN     tab_address_type d
							ON          a.address_type_id = d.address_type_id
					INNER JOIN     tab_address_origin e
							ON          a.address_origin_id = e.address_origin_id
					WHERE
									a.address_id = ?;`
	)

	var address entity.Address
	row := c.conn.QueryRow(query, addressID)
	err := row.Scan(&address.ID, &address.UUID, &address.AddressType.ID, &address.AddressType.Key, &address.AddressType.Name,
		&address.Origin.ID, &address.Origin.Key, &address.Origin.Name, &address.User.ID, &address.User.UUID, &address.User.Name,
		&address.InternalID, &address.Name, &address.Address, &address.Latitude, &address.Longitude, &address.CreatedUser.ID,
		&address.CreatedUser.UUID, &address.CreatedUser.Name)
	if err != nil {
		return entity.Address{}, err
	}

	return address, nil
}

func (c *userRepo) GetAddressByUUID(addressUUID string) (entity.Address, error) {
	const (
		query = `SELECT 
									a.address_id,
									a.address_uuid,
									a.address_type_id,
									d.key address_type_key,
									d.name address_type_name,
									a.address_origin_id,
									e.key address_origin_key,
									e.name address_origin_name,
									b.user_id,
									b.user_uuid,
									b.name user_name,
									a.internal_id,
									a.name,
									a.address,
									a.lat,
									a.long,
									c.user_id created_user_id,
									c.user_uuid created_user_uuid,
									c.name created_user_name
						FROM
													tab_address a
					INNER JOIN     tab_user b
							ON          a.user_id = b.user_id
					INNER JOIN     tab_user c
							ON          a.created_user_id = c.user_id
					INNER JOIN     tab_address_type d
							ON          a.address_type_id = d.address_type_id
					INNER JOIN     tab_address_origin e
							ON          a.address_origin_id = e.address_origin_id
					WHERE
									a.address_uuid = ?;`
	)

	var address entity.Address
	row := c.conn.QueryRow(query, addressUUID)
	err := row.Scan(&address.ID, &address.UUID, &address.AddressType.ID, &address.AddressType.Key, &address.AddressType.Name,
		&address.Origin.ID, &address.Origin.Key, &address.Origin.Name, &address.User.ID, &address.User.UUID, &address.User.Name,
		&address.InternalID, &address.Name, &address.Address, &address.Latitude, &address.Longitude, &address.CreatedUser.ID,
		&address.CreatedUser.UUID, &address.CreatedUser.Name)
	if err != nil {
		return entity.Address{}, err
	}

	return address, nil
}

func (c *userRepo) getAddressSecondaryData(address entity.Address) (entity.Address, error) {
	const (
		query = `SELECT
								a.user_id,
								a.user_uuid,
								a.name user_name,
								b.user_id created_user_id,
								b.user_uuid created_user_uuid,
								b.name created_user_name,
								c.address_type_id,
								c.key address_type_key,
								c.name address_type_name,
								d.address_origin_id,
								d.key address_origin_key,
								d.name address_origin_name
						FROM
								tab_user a,
								tab_user b,
								tab_address_type c,
								tab_address_origin d
						WHERE
								a.user_uuid = ?
						AND b.user_uuid = ?
						AND c.key = ?
						AND d.key = ?;`
	)

	row := c.conn.QueryRow(query, address.User.UUID, address.CreatedUser.UUID, address.AddressType.Key, address.Origin.Key)
	err := row.Scan(&address.User.ID, &address.User.UUID, &address.User.Name, &address.CreatedUser.ID, &address.CreatedUser.UUID, &address.CreatedUser.Name, &address.AddressType.ID, &address.AddressType.Key, &address.AddressType.Name, &address.Origin.ID, &address.Origin.Key, &address.Origin.Name)
	if err != nil {
		return address, err
	}

	return address, nil
}

// GetAll returns a list of all active cards
func (c *userRepo) GetAll() (list []entity.User, err error) {
	return c.parseSet(c.conn.Query(c.getQuery() + " WHERE a.active = 1 "))
}

// GetByID returns a single card data by its ID
func (c *userRepo) GetByID(userID int64) (retVal entity.User, err error) {
	user, err := c.parseSet(c.conn.Query(c.getQuery()+" AND a.user_id = ? ", userID))
	if err != nil {
		return entity.User{}, err
	}

	if len(user) > 0 {
		retVal := user[0]
		retVal.Contacts, err = c.GetContacts(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}

		retVal.Addresses = nil
		retVal.Addresses, err = c.getAddressByUserID(retVal.ID)
		if err != nil {
			return entity.User{}, err
		}
		return retVal, nil
	} else {
		return retVal, nil
	}
}

func toNullString(s string) sql.NullString {
	return sql.NullString{String: s, Valid: s != ""}
}

func toNullInt64(i int64) sql.NullInt64 {
	return sql.NullInt64{Int64: i, Valid: i > 0}
}

func toNullTime(date time.Time) mysql.NullTime {
	return mysql.NullTime{Time: date, Valid: !date.IsZero()}
}
