package twilioroute

import (
	"fmt"
	"log"
	"net/http"
	"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/gorilla/websocket"
	"github.com/labstack/echo"
	"github.com/ttacon/libphonenumber"
)

var (
	instance *controller
	once     sync.Once
	upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
)

type controller struct {
	cfg          *config.Config
	svc          *applicationservice.Service
	tnc          *tncservice.Service
	notification *notificationservice.Service
	broadcast    chan viewmodel.SocketMessage
	clients      map[*websocket.Conn]viewmodel.SocketMessage
}

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,
			broadcast:    make(chan viewmodel.SocketMessage),
			clients:      make(map[*websocket.Conn]viewmodel.SocketMessage),
		}
		go instance.handleMessages()
	})
	return instance
}

type Response struct {
	Message *Message `xml:"Message,omitempty"`
}

type Message struct {
	To   string `xml:"to,attr"`
	Body string `xml:"Body,omitempty"`
}

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) handleTwilio(ctx echo.Context) error {
	var twilioResponse viewmodel.TwilioWebhook
	err := ctx.Bind(&twilioResponse)
	if err != nil {
		fmt.Println("Error binding response: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	// bResp, _ := json.MarshalIndent(twilioResponse, "", "\t")
	// fmt.Println("Twilio Request")
	// fmt.Println(string(bResp))

	var resp Response
	requestMessage := strings.ToUpper(strings.TrimSpace(twilioResponse.Body))
	if requestMessage != "CANCEL RIDE" && requestMessage != "YES" && requestMessage != "NO" && !strings.HasPrefix(requestMessage, "DRIVER") && !strings.HasPrefix(requestMessage, "MEMBER") && requestMessage != "I AM READY" {
		lastRide, err := c.svc.Rides.GetLastRideByPhoneNumber(twilioResponse.From, "", "")
		if err != nil {
			fmt.Println("Error to get the ride: ", err.Error())
		}

		notifications := make([]viewmodel.Notification, 0)
		notifications = append(notifications, c.svc.Notification.GetNotification(lastRide, "sms", "Message to Dispatcher", twilioResponse.Body, true, "message"))
		notifications = append(notifications, c.svc.Notification.GetNotification(lastRide, "app", "Message to Dispatcher", twilioResponse.Body, true, "message"))
		notifications, err = c.svc.Notification.SendNotifications(notifications)
		if err != nil {
			fmt.Println("Error to notify: ", err.Error())
		}
	} else {
		toNumber := twilioResponse.From

		var message string
		var lastRide viewmodel.Ride
		isDriver := false

		num, _ := libphonenumber.Parse(twilioResponse.From, "US")
		if strings.HasPrefix(requestMessage, "MEMBER") {
			isDriver = true
			lastRide, err = c.svc.Rides.GetLastRideByDriversNumber(twilioResponse.From)
			if err != nil {
				fmt.Println("Error to get last driver ride: ", err.Error())
				message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL))
			}
		} else {
			lastRide, err = c.svc.Rides.GetLastRideByPhoneNumber(twilioResponse.From, "", "")
			if err != nil {
				fmt.Println("Error to get last ride: ", err.Error())
				message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL))
			}
		}

		authUser, err := c.svc.Users.GetByUUID(lastRide.CreatedUser.ID, "")
		if err != nil {
			fmt.Println("Error to author of the ride: ", err.Error())
			message = "There was a problem to call your ride"
		}

		if !isDriver {
			if requestMessage == "I AM READY" && lastRide.UUID != "" {
				if (lastRide.Visit.TripType.Key == "roundtrip_call" && (lastRide.TripType.Key == "to_visit" || lastRide.TripType.Key == "from_visit_call")) || lastRide.Visit.TripType.Key == "from_visit_call" {
					var readyRide viewmodel.Ride
					authUser, err := c.svc.Users.GetByUUID(lastRide.CreatedUser.ID, "")
					if err != nil {
						fmt.Println("Error to get created user: ", err.Error())
					}

					if lastRide.TripType.Key == "to_visit" {
						readyRide, err = c.svc.Rides.GetByVisitUUIDAndTripType(lastRide.Visit.UUID, "from_visit_call", authUser)
						if err != nil || readyRide.UUID == "" {
							message = fmt.Sprintf("We received a request to send the return ride from you at %s, but you do not have the a return ride prepared.", libphonenumber.Format(num, libphonenumber.NATIONAL))
						}
					} else {
						readyRide = lastRide
					}

					switch readyRide.Status.Key {
					case "pending", "accepted", "scheduled":
						message = "We already request your return ride, you should receive updates soon."
					case "willCall":
						var lyftRide viewmodel.RideRequest
						lyftRide.RideID = ""
						lyftRide.Destination = readyRide.Route.Destination
						lyftRide.Origin = readyRide.Route.Origin
						name := readyRide.User.Name
						names := strings.Split(name, " ")
						lyftRide.Passenger.FirstName = names[0]
						lyftRide.Passenger.LastName = " "
						lyftRide.Passenger.PhoneNumber = *readyRide.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 {
							fmt.Println("Error to create a lyft ride on twilio: ", err.Error())
							message = "There was a problem to call your ride"
						}

						readyRide, err = c.svc.Rides.UpdateNewRide(readyRide, lyftRide, authUser)
						if err != nil {
							fmt.Println("Error to update ride: ", err.Error())
							message = "There was a problem to call your ride"
						}

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

						message = fmt.Sprintf("Ride requested at %s, for pickup at %s.", lyftRide.RequestAt.Format("03:04 PM"), readyRide.Route.Destination.Name)
					}

				}
			}

			if requestMessage == "CANCEL RIDE" && lastRide.UUID != "" {
				var visitDate time.Time
				if lastRide.VisitTime != nil {
					visitDate = *lastRide.VisitTime
				} else {
					visitDate = *lastRide.VisitDate
				}

				var pickupDate time.Time
				if lastRide.PickupTime != nil {
					pickupDate = *lastRide.PickupTime
				}

				message = fmt.Sprintf("Are you sure you want to cancel a ride to %s visit to %s scheduled for %s?\nReply YES to confirm this cancellation", visitDate.Format("01/02/2006 03:04 PM"), lastRide.Route.Destination.Name, pickupDate.Format("03:04 PM"))
			}

			if requestMessage == "YES" && lastRide.UUID != "" {
				var lyftRide viewmodel.RideRequest
				if authUser.Test {
					lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID})
					if err != nil {
						fmt.Println("Error: ", err.Error())
						message = "There was a problem to find your ride"
					}

					if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil {
						fmt.Println("Error to cancel with Lyft: ", err.Error())
						message = "There was a problem to cancel your ride with our transportation provider"
					}
				} else {
					fmt.Println("In Production")
					lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID})
					if err != nil {
						fmt.Println("Error: ", err.Error())
						message = "There was a problem to find your ride"
					}

					if err = c.tnc.LyftProd.CancelRide(lyftRide); err != nil {
						fmt.Println("Error to cancel with Lyft: ", err.Error())
						message = "There was a problem to cancel your ride with our transportation provider"
					}
				}

				if err = c.svc.Rides.UpdateStatus(lastRide.UUID, "canceled"); err != nil {
					fmt.Println("Error to cancel the ride: ", err.Error())
					message = "There was a problem to cancel your ride on our systems"
				}

				var dateToFormat time.Time
				if lastRide.VisitTime != nil {
					dateToFormat = *lastRide.VisitTime
				} else {
					dateToFormat = *lastRide.VisitDate
				}

				var pickupDate time.Time
				if lastRide.PickupTime != nil {
					pickupDate = *lastRide.PickupTime
				}
				lastRide.Status.Key = "cancelled"
				message = fmt.Sprintf("A ride to a %s visit to %s scheduled for %s has been cancelled.", dateToFormat.Format("01/02/2006 03:04 PM"), lastRide.Route.Destination.Name, pickupDate.Format("03:04 PM"))
			}

			// if requestMessage == "NO" && lastRide.UUID != "" {
			// 	var dateToFormat time.Time
			// 	if lastRide.VisitTime != nil {
			// 		dateToFormat = *lastRide.VisitTime
			// 	} else {
			// 		dateToFormat = *lastRide.VisitDate
			// 	}
			// 	message = fmt.Sprintf("You ride is still scheduled on %s at %s", lastRide.Route.Destination.Name, dateToFormat.Format("01/02/2006 03:04 PM"))
			// }

			if strings.HasPrefix(requestMessage, "DRIVER") && lastRide.UUID != "" {
				driverMessage := strings.Replace(requestMessage, "DRIVER", "", -1)
				driverMessage = strings.TrimSpace(driverMessage) + "\nReply MEMBER and your message to send a message to the member."

				notifications := make([]viewmodel.Notification, 0)
				notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the driver", driverMessage, lastRide.Driver.PhoneNumber, twilioResponse.To))
				notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the driver", requestMessage, *lastRide.CreatedUser.PhoneNumber, *lastRide.User.PhoneNumber))
				notifications = append(notifications, c.getDriverNotification(lastRide, "email", "Message to the driver", driverMessage, *lastRide.CreatedUser.Email, *lastRide.User.Email))
				notifications, err := c.svc.Notification.SendNotifications(notifications)
				if err != nil {
					message = "There was a problem to send a message to the driver."
					fmt.Println("Error to send drivers notification: ", err.Error())
				}

				message = "Your message was sent to the driver via SMS."
			}
		} else {
			driverMessage := strings.Replace(requestMessage, "MEMBER", "", -1)
			driverMessage = strings.TrimSpace(driverMessage) + "\nReply DRIVER and your message to send a message to the driver."

			userPhoneNumber := ""
			createUserPhoneNumber := ""
			userEmail := ""
			createdUserEmail := ""
			if lastRide.User.PhoneNumber != nil {
				userPhoneNumber = *lastRide.User.PhoneNumber
			}

			if lastRide.CreatedUser.PhoneNumber != nil {
				createUserPhoneNumber = *lastRide.CreatedUser.PhoneNumber
			}

			if lastRide.User.Email != nil {
				userEmail = *lastRide.User.Email
			}

			if lastRide.CreatedUser.Email != nil {
				createdUserEmail = *lastRide.CreatedUser.Email
			}

			notifications := make([]viewmodel.Notification, 0)
			notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the member from the driver", driverMessage, userPhoneNumber, twilioResponse.To))
			notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the member from the driver", requestMessage, createUserPhoneNumber, userPhoneNumber))
			notifications = append(notifications, c.getDriverNotification(lastRide, "email", "Message to the member from the driver", driverMessage, createdUserEmail, userEmail))
			notifications, err := c.svc.Notification.SendNotifications(notifications)
			if err != nil {
				message = "There was a problem to send a message to the member."
				fmt.Println("Error to send member notification: ", err.Error())
			}

			message = "Your message was sent to the member via SMS."
		}
		resp.Message = &Message{
			To:   toNumber,
			Body: message,
		}
	}
	return ctx.XML(http.StatusOK, resp)
}

func (c *controller) handleMessages() {
	for {
		msg := <-c.broadcast
		for client := range c.clients {
			if c.clients[client].From == msg.To {
				err := client.WriteJSON(msg)
				if err != nil {
					log.Printf("error: %v", err)
					client.Close()
					delete(c.clients, client)
				}
			}
		}
	}
}

func (c *controller) handleSocket(ctx echo.Context) error {
	ws, err := upgrader.Upgrade(ctx.Response(), ctx.Request(), nil)
	if err != nil {
		return err
	}
	defer ws.Close()

	c.clients[ws] = viewmodel.SocketMessage{Filled: false}
	for {
		var msg viewmodel.SocketMessage
		err := ws.ReadJSON(&msg)
		if err != nil {
			log.Printf("error: %v", err)
			delete(c.clients, ws)
			break
		}
		msg.Filled = true
		c.clients[ws] = msg
	}
	return nil
}
