package applicationservice

import (
	"fmt"
	"time"

	"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/domain/entity"
	"bitbucket.org/nemt/nemt-portal-api/domain/service"
	"bitbucket.org/nemt/nemt-portal-api/infra/errors"
)

// rideService holds methods to user application service
type rideService struct {
	svc       *service.Service
	mapEntity *entitymapping.Mapper
}

// newUserService returns a userService instance
func newRideService(svc *service.Service, mapper *entitymapping.Mapper) *rideService {
	return &rideService{
		svc:       svc,
		mapEntity: mapper,
	}
}

func (s *rideService) UpdateNewRide(ride viewmodel.Ride, lyftRide viewmodel.RideRequest, user viewmodel.User) (viewmodel.Ride, error) {
	webhookModel := viewmodel.WebhookResponse{
		Ride:  ride,
		Event: lyftRide,
	}
	webhook := s.mapEntity.Ride.ToWebhookEntity(webhookModel)
	eUser := s.mapEntity.User.ToUserEntity(user)
	oldRide, err := s.svc.Rides.GetByUUID(ride.UUID, eUser)
	if err != nil {
		return viewmodel.Ride{}, err
	}

	requestMS := (oldRide.PickupTime.UnixNano() / int64(time.Millisecond))
	generateDate := time.Now()
	generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
	if webhook.Ride.RequestDate == nil {
		webhook.Ride.RequestDate = oldRide.PickupTime
	}
	if webhook.Ride.RequestMiliseconds == nil {
		webhook.Ride.RequestMiliseconds = &requestMS
	}
	if webhook.Ride.GenerateDate == nil {
		webhook.Ride.GenerateDate = &generateDate
	}
	if webhook.Ride.GenerateMiliseconds == nil {
		webhook.Ride.GenerateMiliseconds = &generateMS
	}

	switch webhook.Ride.Status.Key {
	case "scheduled":
		webhook.Ride.Route.ETA = oldRide.PickupTime.UnixNano() / int64(time.Second)
	case "accepted":
		if lyftRide.Origin.ETASeconds != nil {
			webhook.Ride.Route.ETA = *lyftRide.Origin.ETASeconds
		} else {
			webhook.Ride.Route.ETA = 0
		}
	case "arrived", "pickedUp":
		if lyftRide.Destination.ETASeconds != nil {
			webhook.Ride.Route.ETA = *lyftRide.Destination.ETASeconds
		} else {
			webhook.Ride.Route.ETA = 0
		}
	case "droppedOff":
		webhook.Ride.Route.ETA = 0
	}
	webhook.Ride.Route.Duration = int64(time.Now().Sub(*oldRide.PickupTime).Seconds())

	webhook.Ride.InternalID = lyftRide.RideID
	webhook.Ride.ID = oldRide.ID
	webhook.Ride.UUID = oldRide.UUID
	webhook.Ride.Driver.ID = oldRide.Driver.ID
	webhook.Ride.Passenger.ID = oldRide.Passenger.ID
	webhook.Ride.Route.ID = oldRide.Route.ID
	webhook.Ride.Type.ID = oldRide.Type.ID
	webhook.Ride.User = oldRide.User
	webhook.Ride.CreatedUser = oldRide.CreatedUser
	webhook.Ride.Vehicle.ID = oldRide.Vehicle.ID
	webhook.Ride.Visit = oldRide.Visit
	webhook.Ride.VisitDate = oldRide.VisitDate
	webhook.Ride.VisitTime = oldRide.VisitTime
	//webhook.Ride.PickupTime = oldRide.PickupTime

	entityRide, err := s.svc.Rides.Update(webhook)
	if err != nil {
		return viewmodel.Ride{}, err
	}

	return s.mapEntity.Ride.ToRideModel(entityRide), nil
}

func (s *rideService) Update(ride viewmodel.WebhookResponse) (viewmodel.Ride, error) {
	webhook := s.mapEntity.Ride.ToWebhookEntity(ride)
	oldRide, err := s.svc.Rides.GetByInternalID(ride.Event.RideID)
	if err != nil {
		oldRide, err = s.svc.Rides.GetByInternalPassengerID(*ride.Event.Passenger.UserID)
		if err != nil {
			return viewmodel.Ride{}, err
		}
	}

	requestMS := (oldRide.PickupTime.UnixNano() / int64(time.Millisecond))
	generateDate := time.Now()
	generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
	if webhook.Ride.RequestDate == nil {
		webhook.Ride.RequestDate = oldRide.PickupTime
	}
	if webhook.Ride.RequestMiliseconds == nil {
		webhook.Ride.RequestMiliseconds = &requestMS
	}
	if webhook.Ride.GenerateDate == nil {
		webhook.Ride.GenerateDate = &generateDate
	}
	if webhook.Ride.GenerateMiliseconds == nil {
		webhook.Ride.GenerateMiliseconds = &generateMS
	}

	switch webhook.Ride.Status.Key {
	case "scheduled":
		webhook.Ride.Route.ETA = oldRide.PickupTime.UnixNano() / int64(time.Second)
	case "accepted":
		if ride.Event.Origin.ETASeconds != nil {
			webhook.Ride.Route.ETA = *ride.Event.Origin.ETASeconds
		} else {
			webhook.Ride.Route.ETA = 0
		}
	case "arrived", "pickedUp":
		if ride.Event.Destination.ETASeconds != nil {
			webhook.Ride.Route.ETA = *ride.Event.Destination.ETASeconds
		} else {
			webhook.Ride.Route.ETA = 0
		}
	case "droppedOff":
		webhook.Ride.Route.ETA = 0
	}
	webhook.Ride.Route.Duration = int64(time.Now().Sub(*oldRide.PickupTime).Seconds())

	webhook.Ride.InternalID = ride.Event.RideID
	webhook.Ride.ID = oldRide.ID
	webhook.Ride.UUID = oldRide.UUID
	webhook.Ride.Driver.ID = oldRide.Driver.ID
	webhook.Ride.Passenger.ID = oldRide.Passenger.ID
	webhook.Ride.Route.ID = oldRide.Route.ID
	webhook.Ride.Type.ID = oldRide.Type.ID
	webhook.Ride.User = oldRide.User
	webhook.Ride.CreatedUser = oldRide.CreatedUser
	webhook.Ride.Vehicle.ID = oldRide.Vehicle.ID
	webhook.Ride.Visit = oldRide.Visit
	webhook.Ride.VisitDate = oldRide.VisitDate
	webhook.Ride.VisitTime = oldRide.VisitTime
	webhook.Ride.PickupTime = oldRide.PickupTime

	entityRide, err := s.svc.Rides.Update(webhook)
	if err != nil {
		return viewmodel.Ride{}, err
	}

	return s.mapEntity.Ride.ToRideModel(entityRide), nil
}

// Save a new ride
func (s *rideService) Save(ride viewmodel.RideRequest) (viewmodel.Ride, error) {
	rideEntity := s.mapEntity.Ride.ToRideEntity(ride)

	if ride.Visit.UUID == "" {
		visit := entity.Visit{}
		visit.User = rideEntity.User
		visit.Pickup = rideEntity.Route.Origin
		visit.Notes = &rideEntity.Note
		visit.PickupDatetime = *rideEntity.PickupTime
		visit.VisitDatetime = *rideEntity.VisitTime
		visit.CreatedUser.UUID = ride.CreateUserUUID
		visit.ExternalID = ride.VisitExternalID
		visit.TripType = rideEntity.Visit.TripType
		visit.ReturnDate = ride.ReturnTime

		address, _ := s.svc.Users.GetAddressByUUID(rideEntity.Route.Destination.ID)
		createdUser, err := s.svc.Users.GetByUUID(ride.CreateUserUUID, "")
		if err != nil {
			fmt.Println("Ride Application.Save: Error to get User: ", err.Error())
			return viewmodel.Ride{}, errors.Wrap(err)
		}

		if address.ID > 0 {
			visit.DestinationAddressID = address.ID
			provider, err := s.svc.Provider.GetByMukID(address.InternalID, createdUser)
			if err != nil {
				fmt.Println("Ride Application.Save: Error to get Provider: ", err.Error())
				return viewmodel.Ride{}, errors.Wrap(err)
			}
			visit.Provider = provider
		} else {
			provider, err := s.svc.Provider.GetByMukID(rideEntity.Route.Destination.ID, createdUser)
			if err != nil {
				fmt.Println("Ride Application.Save: Error to get Provider by Route: ", err.Error())
				return viewmodel.Ride{}, errors.Wrap(err)
			}
			visit.Provider = provider
		}

		visit, err = s.svc.Visits.Create(visit)
		if err != nil {
			fmt.Println("Ride Application.Save: Error creating visit: ", err.Error())
			return viewmodel.Ride{}, errors.Wrap(err)
		}
		rideEntity.Visit = visit
	} else {
		user, err := s.svc.Users.GetByUUID(ride.CreateUserUUID, "")
		if err != nil {
			fmt.Println("Ride Application.Save: Error getting User: ", err.Error())
			return viewmodel.Ride{}, errors.Wrap(err)
		}

		visit, err := s.svc.Visits.GetByUUID(ride.Visit.UUID, user)
		if err != nil {
			fmt.Println("Ride Application.Save: Error getting Visit: ", err.Error())
			return viewmodel.Ride{}, errors.Wrap(err)
		}
		rideEntity.Visit = visit
	}

	retVal, err := s.svc.Rides.Save(rideEntity)
	if err != nil {
		fmt.Println("Ride Application.Save: Error saving ride: ", err.Error())
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	return s.mapEntity.Ride.ToRideModel(retVal), err
}

// GetAll returns a list of users
func (s *rideService) GetAll(user viewmodel.User) ([]viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetAll(eUser)
	if err != nil {
		return nil, errors.Wrap(err)
	}

	return s.mapEntity.Ride.ToRideModelSlice(retVal), err
}

// GetByUUID returns a specific Ride
func (s *rideService) GetByUUID(uuid string, user viewmodel.User) (viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetByUUID(uuid, eUser)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	if retVal.Visit.ID > 0 {
		retVal.Visit, err = s.svc.Visits.GetByID(retVal.Visit.ID, eUser)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}
	}

	return s.mapEntity.Ride.ToRideModel(retVal), err
}

func (s *rideService) GetByVisitUUID(visitUUID string, user viewmodel.User) ([]viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetByVisitUUID(visitUUID, eUser)
	if err != nil {
		return nil, errors.Wrap(err)
	}

	visit, err := s.svc.Visits.GetByUUID(visitUUID, eUser)
	if err != nil {
		return nil, errors.Wrap(err)
	}

	for i, _ := range retVal {
		retVal[i].Visit = visit
	}

	return s.mapEntity.Ride.ToRideModelSlice(retVal), err
}

func (s *rideService) GetByVisitUUIDAndTripType(visitUUID string, tripTypeKey string, user viewmodel.User) (viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetByVisitUUIDAndTripType(visitUUID, tripTypeKey, eUser)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	visit, err := s.svc.Visits.GetByUUID(visitUUID, eUser)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	retVal.Visit = visit
	return s.mapEntity.Ride.ToRideModel(retVal), err
}

// GetByUUID returns a specific Ride
func (s *rideService) GetByUUIDAndUserUUID(uuid string, user_uuid string) (viewmodel.Ride, error) {
	retVal, err := s.svc.Rides.GetByUUIDAndUserUUID(uuid, user_uuid)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	if retVal.Visit.ID > 0 {
		user, err := s.svc.Users.GetByID(retVal.CreatedUser.ID)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}

		retVal.Visit, err = s.svc.Visits.GetByID(retVal.Visit.ID, user)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}
	}

	return s.mapEntity.Ride.ToRideModel(retVal), err
}

// GetByInternalID returns a specific Ride
func (s *rideService) GetByInternalID(internalID string) (viewmodel.Ride, error) {
	retVal, err := s.svc.Rides.GetByInternalID(internalID)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	if retVal.Visit.ID > 0 {
		user, err := s.svc.Users.GetByID(retVal.CreatedUser.ID)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}

		retVal.Visit, err = s.svc.Visits.GetByID(retVal.Visit.ID, user)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}
	}
	return s.mapEntity.Ride.ToRideModel(retVal), err
}

// GetByUserID returns a list of rides for an user
func (s *rideService) GetByUserID(userID int64, user viewmodel.User) ([]viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetByUserID(userID, eUser)
	if err != nil {
		return []viewmodel.Ride{}, errors.Wrap(err)
	}

	return s.mapEntity.Ride.ToRideModelSlice(retVal), err
}

// UpdateStatus change the status of the ride
func (s *rideService) UpdateStatus(rideUUID string, status string) error {
	err := s.svc.Rides.UpdateStatus(rideUUID, status)
	if err != nil {
		return errors.Wrap(err)
	}

	return nil
}

// GetByUserID returns a list of rides for an user
func (s *rideService) GetByUserUUID(userUUID string, user viewmodel.User) ([]viewmodel.Ride, error) {
	eUser := s.mapEntity.User.ToUserEntity(user)
	retVal, err := s.svc.Rides.GetByUserUUID(userUUID, eUser)
	if err != nil {
		return []viewmodel.Ride{}, errors.Wrap(err)
	}

	return s.mapEntity.Ride.ToRideModelSlice(retVal), err
}

func (s *rideService) GetLastRideByPhoneNumber(phoneNumber string, notificationType string, status string) (viewmodel.Ride, error) {
	// if notificationType == ""{
	// 	notificationType = "message"
	// }

	if status == "" {
		status = "sms"
	}

	notification, err := s.svc.Notification.GetLastNotificationFromPhoneNumber(notificationType, phoneNumber, status)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	user, err := s.svc.Users.GetByID(notification.CreatedUser.ID)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	retVal, err := s.svc.Rides.GetByID(notification.Ride.ID, user)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	if retVal.Visit.ID > 0 {
		retVal.Visit, err = s.svc.Visits.GetByID(retVal.Visit.ID, user)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}
	}
	return s.mapEntity.Ride.ToRideModel(retVal), err
}

func (s *rideService) GetLastRideByDriversNumber(phoneNumber string) (viewmodel.Ride, error) {
	retVal, err := s.svc.Rides.GetLastRideByDriversNumber(phoneNumber)
	if err != nil {
		return viewmodel.Ride{}, errors.Wrap(err)
	}

	if retVal.Visit.ID > 0 {
		user, err := s.svc.Users.GetByID(retVal.CreatedUser.ID)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}

		retVal.Visit, err = s.svc.Visits.GetByID(retVal.Visit.ID, user)
		if err != nil {
			return viewmodel.Ride{}, errors.Wrap(err)
		}
	}
	return s.mapEntity.Ride.ToRideModel(retVal), err
}
