package datamysql

import (
	"database/sql"
	"fmt"

	"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"
)

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

func newVisitRepo(conn executor) *visitRepo {
	return &visitRepo{
		conn: conn,
	}
}

func (c *visitRepo) getProfileQuery(user entity.User) (query string, where string, err error) {
	if len(user.Profiles) > 0 {
		for _, p := range user.Profiles {
			switch p.Key {
			case "AD", "BCBSIAD", "BDCAD", "PLANAD":
				return
			case "SP", "SPT", "VIRPT":
				switch p.Organization.Type.Key {
				case "techsupport", "bcbsi", "bcbsa", "plan":
					return
				case "provider":
					query = ` INNER JOIN viw_visit_provider AS o 	ON  o.visit_id    = a.visit_id `
					where = fmt.Sprintf(`	AND ( o.organization_uuid = '%s' OR o.parent_organization_uuid = '%s' ) `, p.Organization.UUID, p.Organization.UUID)
					return
				}
			}
		}

		if query == "" && where == "" {
			return "", "", fmt.Errorf("Invalid Query")
		} else {
			return
		}
	} else {
		return "", "", fmt.Errorf("User has no profile to search")
	}
}

func (c *visitRepo) getQuery() string {
	return `SELECT 
								a.visit_id,
								a.visit_uuid,
								a.visit_status_id,
								b.visit_status_key,
								b.visit_status,
								a.user_id,
								c.user_uuid,
								c.name user_name,
								c.subscriber_id user_member,
								c.birth_date user_birth_date,
								c.gender user_gender,
								a.visit_datetime,
								a.pickup_datetime,
								IFNULL(a.notes, '') notes,
								a.created_user_id,
								d.user_uuid created_user_uuid,
								d.name created_user_name,
								a.created_date,
								a.updated_date,
								IFNULL(a.visit_duration, 0) visit_duration,
								a.visit_external_id,
								e.trip_type_id,
								e.trip_type_key,
								e.trip_type,
								IFNULL(a.pickup_id, '') visit_pickup_id,
								IFNULL(a.pickup_address_id, 0) pickup_address_id,
								IFNULL(a.address_id, 0) address_id,
								IFNULL(a.provider_id, 0) provider_id, 
								IFNULL(f.provider_uuid, '') provider_uuid, 
								IFNULL(f.provider_internal_id, '') provider_internal_id, 
								IFNULL(f.provider_muk_id, '') provider_muk_id, 
								IFNULL(f.provider_name, '') provider_name 
					FROM 
										tab_visit AS a
				INNER JOIN tab_visit_status AS b
						ON      a.visit_status_id = b.visit_status_id
				INNER JOIN tab_user AS c
						ON      c.user_id = a.user_id
				INNER JOIN tab_user AS d
						ON      d.user_id = a.created_user_id
				INNER JOIN tab_trip_type AS e
						ON      e.trip_type_id = a.trip_type_id
				INNER JOIN tab_provider AS f
						ON      f.provider_id  = a.provider_id `
}

func (c *visitRepo) GetAll(user entity.User) ([]entity.Visit, error) {
	query, where, err := c.getProfileQuery(user)
	if err != nil {
		return nil, err
	}

	query = c.getQuery() + query + " WHERE 1 = 1 and c.active = 1 " + where

	return c.parseSet(c.conn.Query(query))
}

func (c *visitRepo) GetByUUID(visitUUID string, user entity.User) (entity.Visit, error) {
	query, where, err := c.getProfileQuery(user)
	if err != nil {
		return entity.Visit{}, err
	}

	query = c.getQuery() + query + " WHERE a.visit_uuid = ? " + where
	return c.parseEntity(c.conn.QueryRow(query, visitUUID))
}

func (c *visitRepo) GetByID(visitID int64, user entity.User) (entity.Visit, error) {
	query, where, err := c.getProfileQuery(user)
	if err != nil {
		return entity.Visit{}, err
	}

	query = c.getQuery() + query + " WHERE a.visit_id = ? " + where
	return c.parseEntity(c.conn.QueryRow(query, visitID))
}

// parseSet parses a result set result to an entity array
func (c *visitRepo) parseSet(rows *sql.Rows, err error) ([]entity.Visit, error) {
	if err != nil {
		return nil, errors.Wrap(err)
	}
	result := make([]entity.Visit, 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 *visitRepo) parseEntity(row scanner) (retVal entity.Visit, err error) {
	var birthDate mysql.NullTime
	var gender sql.NullString

	err = row.Scan(
		&retVal.ID, &retVal.UUID, &retVal.Status.ID, &retVal.Status.Key, &retVal.Status.Value, &retVal.User.ID, &retVal.User.UUID, &retVal.User.Name, &retVal.User.Member, &birthDate, &gender, &retVal.VisitDatetime, &retVal.PickupDatetime, &retVal.Notes, &retVal.CreatedUser.ID, &retVal.CreatedUser.UUID, &retVal.CreatedUser.Name, &retVal.Created, &retVal.Updated, &retVal.VisitDuration, &retVal.ExternalID, &retVal.TripType.ID, &retVal.TripType.Key, &retVal.TripType.Value, &retVal.Pickup.ID, &retVal.PickupAddressID, &retVal.DestinationAddressID, &retVal.Provider.ProviderID, &retVal.Provider.ProviderUUID, &retVal.Provider.InternalID, &retVal.Provider.MukID, &retVal.Provider.Name)

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

	if gender.Valid {
		retVal.User.Gender = gender.String
	} else {
		retVal.User.Gender = "U"
	}

	return retVal, errors.Wrap(err)
}

func (c *visitRepo) Create(visit entity.Visit) (entity.Visit, error) {
	const (
		statusQuery = `SELECT a.visit_status_id, a.visit_status_key, a.visit_status, b.user_id, b.name user_name, c.user_id create_user_id, c.name create_user_name, d.trip_type_id, d.trip_type_key, d.trip_type FROM tab_visit_status a, tab_user b, tab_user c, tab_trip_type d WHERE a.visit_status_key = ? AND b.user_uuid = ? AND c.user_uuid = ? AND d.trip_type_key = ?;`
		query       = `INSERT INTO tab_visit(visit_uuid, visit_status_id, user_id, visit_datetime, pickup_datetime, notes, created_user_id, visit_duration, visit_external_id, return_date, trip_type_id, pickup_id, pickup_address_id, address_id, provider_id)
									 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
	)

	retVal := visit

	returnDate := mysql.NullTime{
		Valid: (retVal.ReturnDate != nil),
	}
	if retVal.ReturnDate != nil {
		returnDate.Time = *retVal.ReturnDate
	}

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

	if retVal.Status.Key == "" {
		retVal.Status.Key = "accepted"
	}

	row := c.conn.QueryRow(statusQuery, retVal.Status.Key, retVal.User.UUID, retVal.CreatedUser.UUID, retVal.TripType.Key)
	if err := row.Scan(&retVal.Status.ID, &retVal.Status.Key, &retVal.Status.Value, &retVal.User.ID, &retVal.User.Name, &retVal.CreatedUser.ID, &retVal.CreatedUser.Name, &retVal.TripType.ID, &retVal.TripType.Key, &retVal.TripType.Value); err != nil {
		fmt.Println("data.Visit: Error getting base data: ", err.Error())
		return retVal, err
	}

	results, err := c.conn.Exec(query, retVal.UUID, retVal.Status.ID, retVal.User.ID, retVal.VisitDatetime, retVal.PickupDatetime, retVal.Notes, retVal.CreatedUser.ID, toNullInt64(retVal.VisitDuration), retVal.ExternalID, returnDate, retVal.TripType.ID, retVal.Pickup.ID, retVal.PickupAddressID, retVal.DestinationAddressID, retVal.Provider.ProviderID)
	if err != nil {
		fmt.Println("data.Visit: Error inserting visit: ", err.Error())
		return retVal, err
	}

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

	return retVal, nil
}
