package externalroute

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/notificationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/tncservice"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/infra/config"
	"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
	"github.com/labstack/echo"
)

var (
	instance *controller
	once     sync.Once
)

type controller struct {
	cfg          *config.Config
	svc          *applicationservice.Service
	tnc          *tncservice.Service
	notification *notificationservice.Service
}

func controllerInstance(cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) *controller {
	once.Do(func() {
		instance = &controller{
			cfg:          cfg,
			svc:          svc,
			tnc:          tnc,
			notification: notification,
		}
	})
	return instance
}

func (c *controller) getDriverNotification(ride viewmodel.Ride, notificationType string, subject string, message string, to string, from string) viewmodel.Notification {
	retVal := viewmodel.Notification{
		Type:        notificationType,
		Message:     message,
		Subject:     subject,
		Ride:        ride,
		User:        ride.User,
		CreatedUser: ride.CreatedUser,
		To:          to,
		From:        from,
	}

	return retVal
}

func (c *controller) handleCancel(ctx echo.Context) error {
	var rideUUID = ctx.Param("ride_uuid")
	var userUUID = ctx.Param("user_uuid")

	ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	lyftRide := viewmodel.RideRequest{RideID: ride.InternalID}
	if ride.Status.Key == "scheduled" && !strings.Contains(ride.InternalID, "s_") {
		lyftRide.RideID = "s_" + ride.InternalID
	}

	if authUser.Test {
		if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil {
			if err.Error() != "ride_not_found" {
				fmt.Println("Error to cancel with Lyft: ", err.Error())
				return routeutils.HandleAPIError(ctx, err)
			}
		}
	} else {
		fmt.Println("In Production")
		if err = c.tnc.LyftProd.CancelRide(lyftRide); err != nil {
			fmt.Println("Error to cancel with Lyft: ", err.Error())
			return routeutils.HandleAPIError(ctx, err)
		}
	}

	err = c.svc.Rides.UpdateStatus(rideUUID, "canceled")
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	// ride.Status.Key = "canceled"

	// err = c.svc.Notification.SendNotification(ride.Status.Key, ride, viewmodel.RideRequest{})
	// if err != nil {
	// 	fmt.Println("Error to notify: ", err.Error())
	// }

	ctx.Response().Header().Set("Content-Type", "application/json")
	return routeutils.ResponseNoContent(ctx, nil)
}

func (c *controller) handleMessage(ctx echo.Context) error {
	var rideUUID = ctx.Param("ride_uuid")
	var userUUID = ctx.Param("user_uuid")

	ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	message := make(map[string]string)
	err = ctx.Bind(&message)
	if err != nil || message["message"] == "" {
		return routeutils.ResponseAPIValidationError(ctx, "message body param is mandatory")
	}

	if err != nil || message["to"] == "" {
		return routeutils.ResponseAPIValidationError(ctx, "to param is mandatory")
	}
	notifications := make([]viewmodel.Notification, 0)
	if message["to"] == "driver" {
		notifications = append(notifications, c.getDriverNotification(ride, "sms", "A message to driver", message["message"], ride.Driver.PhoneNumber, *ride.User.PhoneNumber))
	} else {
		notifications = append(notifications, c.getDriverNotification(ride, "sms", "A message to dispatcher", message["message"], *ride.CreatedUser.PhoneNumber, *ride.User.PhoneNumber))
		notifications = append(notifications, c.getDriverNotification(ride, "email", "A message to dispatcher", message["message"], *ride.CreatedUser.Email, *ride.User.Email))
	}

	notifications, err = c.svc.Notification.SendNotifications(notifications)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, notifications)
}

func (c *controller) handle(ctx echo.Context) error {
	var rideUUID = ctx.Param("ride_uuid")
	var userUUID = ctx.Param("user_uuid")

	ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
		var lyftRide viewmodel.RideRequest
		var err error
		if authUser.Test {
			lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
		} else {
			lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
		}
		if err != nil {
			fmt.Println("Error: ", err.Error())
			return routeutils.HandleAPIError(ctx, err)
		}

		ride.Route.Location = ride.Route.Origin
		ride.Route.Location.Address = ""
		ride.Route.Location.Name = ""

		switch ride.Status.Key {
		case "accepted":
			if lyftRide.Origin.ETASeconds != nil {
				ride.Route.ETA = *lyftRide.Origin.ETASeconds
			} else {
				ride.Route.ETA = 0
				if c.cfg.App.Debug {
					ride.Route.ETA = 350
				}
			}
		case "pickedUp":
		case "arrived":
			if lyftRide.Destination.ETASeconds != nil {
				ride.Route.ETA = *lyftRide.Destination.ETASeconds
			} else {
				ride.Route.ETA = 0
				if c.cfg.App.Debug {
					ride.Route.ETA = 350
				}
			}
		}

		if lyftRide.Location.Latitude != 0 && lyftRide.Location.Longitude != 0 {
			ride.Route.Location.Latitude = lyftRide.Location.Latitude
			ride.Route.Location.Longitude = lyftRide.Location.Longitude
			ride.Route.Location.Bearing = lyftRide.Location.Bearing
		}
	}

	ride.InternalID = ""
	ride.User.Gender = nil
	ride.User.Email = nil
	ride.User.PhoneNumber = nil
	ride.Passenger = viewmodel.UserLyft{}
	// ride.Visit = viewmodel.Visit{
	// 	ExternalID: ride.Visit.ExternalID,
	// }
	ride.CreatedUser = viewmodel.User{}

	return routeutils.ResponseAPIOK(ctx, ride)
}

func (c *controller) handleReady(ctx echo.Context) error {
	var rideUUID = ctx.Param("ride_uuid")
	var userUUID = ctx.Param("user_uuid")

	ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	var nextRide viewmodel.Ride
	if ride.TripType.Key == "from_visit_call" && ride.Status.Key == "willCall" {
		nextRide = ride
	} else if ride.Visit.TripType.Key == "roundtrip_call" && ride.TripType.Key == "to_visit" {
		roundTripRide, err := c.svc.Rides.GetByVisitUUIDAndTripType(ride.Visit.UUID, "from_visit_call", authUser)
		if err != nil {
			fmt.Println("Error to get next ride: ", err.Error())
			return routeutils.HandleAPIError(ctx, err)
		}
		nextRide = roundTripRide
	} else {
		fmt.Println("Error on get next ride ")
		return routeutils.ResponseAPINotFoundError(ctx)
	}

	var lyftRide viewmodel.RideRequest
	if authUser.Test {
		lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
	} else {
		fmt.Println("In Production")
		lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
	}
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	lyftRide.RideID = ""
	lyftRide.Destination = ride.Route.Destination
	lyftRide.Origin = ride.Route.Origin

	name := nextRide.User.Name
	names := strings.Split(name, " ")
	lyftRide.Passenger.FirstName = names[0]
	lyftRide.Passenger.LastName = " "
	lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber
	lyftRide.RideType = "lyft"

	if authUser.Test {
		lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
	} else {
		fmt.Println("In Production")
		lyftRide, err = c.tnc.LyftProd.RequestRide(lyftRide)
	}
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	currentTime := time.Now()
	lyftRide.PickupTime = &currentTime
	lyftRide.ReturnTime = &currentTime
	nextRide.PickupTime = &currentTime

	nextRide, err = c.svc.Rides.UpdateNewRide(nextRide, lyftRide, authUser)
	if err != nil {
		fmt.Println("Error: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	go func() {
		err = c.svc.Notification.SendNotification(lyftRide.Status, nextRide, lyftRide)
		if err != nil {
			fmt.Println("Error to notify user: ", err.Error())
		}
	}()

	return routeutils.ResponseAPIOK(ctx, nextRide)
}
