package applicationservice

import (
	"fmt"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/pquerna/ffjson/ffjson"

	"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
	"bitbucket.org/nemt/nemt-portal-api/application/notificationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/domain/service"
	"bitbucket.org/nemt/nemt-portal-api/infra/config"
	"github.com/gorilla/websocket"
	"google.golang.org/api/googleapi/transport"
	urlshortener "google.golang.org/api/urlshortener/v1"
	gomail "gopkg.in/gomail.v2"
	redis "gopkg.in/redis.v5"
)

const (
	NotificationTypeEmail = "email"
	NOtificationTypeSMS   = "sms"
	NotificationTypeAPP   = "app"

	StatusPending    = "pending"
	StatusAccepted   = "accepted"
	StatusArrived    = "arrived"
	StatusPickedUp   = "pickedUp"
	StatusDroppedOff = "droppedOff"
	StatusCanceled   = "canceled"
	StatusScheduled  = "scheduled"
	StatusWillCall   = "willCall"

	hourMinuteAMPM = "03:04 PM"
)

// providerService holds methods to provider application service
type notificationService struct {
	svc          *service.Service
	mapEntity    *entitymapping.Mapper
	notification *notificationservice.Service
	cfg          *config.Config
	store        *Store
	pubSub       *redis.PubSub
	redisConn    *redis.Client
}

// newProviderService returns a providerService instance
func newNotificationService(svc *service.Service, mapper *entitymapping.Mapper, notification *notificationservice.Service, cfg *config.Config) *notificationService {
	redisClient := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    "master01",
		SentinelAddrs: []string{fmt.Sprintf("%s:%v", cfg.Cache.Server, cfg.Cache.Port)},
		Password:      cfg.Cache.Pass,
		DB:            cfg.Cache.DB,
	})

	pubSub, err := redisClient.Subscribe()
	if err != nil {
		panic(err)
	}

	return &notificationService{
		svc:          svc,
		mapEntity:    mapper,
		notification: notification,
		cfg:          cfg,
		redisConn:    redisClient,
		store: &Store{
			Users:       make(map[string]*UserNotification),
			redisClient: redisClient,
			pubSub:      pubSub,
		},
		pubSub: pubSub,
	}
}

func (s *notificationService) secondsToMinutes(inSeconds int64) string {
	minutes := inSeconds / 60
	seconds := inSeconds % 60
	str := fmt.Sprintf("%v minutes", minutes)

	if seconds > 0 {
		str += fmt.Sprintf(" %v seconds", seconds)
	}

	return str
}

func (s *notificationService) getReadyURLShortened(ride viewmodel.Ride) (string, error) {
	const (
		url = "https://portal.bcbsinstitute.com/#/ride/%s/%s/ready"
	)

	svc, err := urlshortener.New(&http.Client{
		Transport: &transport.APIKey{Key: s.cfg.GoogleShortener.APIKey},
	})
	if err != nil {
		return "", err
	}

	userURL := fmt.Sprintf(url, ride.UUID, ride.User.ID)
	shortURL, err := svc.Url.Insert(&urlshortener.Url{
		Kind:    "urlshortener#url", // Not really needed
		LongUrl: userURL,
	}).Do()
	if err != nil {
		return "", err
	}

	return shortURL.Id, nil
}

func (s *notificationService) getURLShortened(ride viewmodel.Ride) (string, error) {
	const (
		url = "https://portal.bcbsinstitute.com/#/ride/%s/%s"
	)

	svc, err := urlshortener.New(&http.Client{
		Transport: &transport.APIKey{Key: s.cfg.GoogleShortener.APIKey},
	})
	if err != nil {
		return "", err
	}

	userURL := fmt.Sprintf(url, ride.UUID, ride.User.ID)
	shortURL, err := svc.Url.Insert(&urlshortener.Url{
		Kind:    "urlshortener#url", // Not really needed
		LongUrl: userURL,
	}).Do()
	if err != nil {
		return "", err
	}

	return shortURL.Id, nil
}

func (s *notificationService) SendNotification(state string, ride viewmodel.Ride, lyftRide viewmodel.RideRequest) error {
	notifications := make([]viewmodel.Notification, 0)
	url, err := s.getURLShortened(ride)
	if err != nil {
		fmt.Println("Error to short url: ", err)
	}

	loc, _ := time.LoadLocation("America/Chicago")
	switch state {
	case StatusWillCall:
		visitTime := ride.VisitDate.In(loc).Format(hourMinuteAMPM)
		visitDate := ride.VisitDate.In(loc).Format("01/02/2006")

		messageMemberSMS := fmt.Sprintf(MessageSMSWillCallMember, visitTime, ride.Route.Destination.Name, visitDate)
		messageMemberSMS += "\n\n" + MessageSMSReplyOrCancel
		messageMemberEmail := fmt.Sprintf(MessageEmailWillCallMember, visitTime, ride.Route.Destination.Name, visitDate)
		//messageMemberApp := fmt.Sprintf(MessageAppPendingMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		urlReady, err := s.getReadyURLShortened(ride)
		if err != nil {
			fmt.Println(err)
		}

		messageMemberSMS += "\n" + fmt.Sprintf(MessageSMSIAmReadyOrClick, ride.Route.Destination.Name, urlReady)
		messageMemberEmail += "\n" + fmt.Sprintf(MessageEmailIAmReadyOrClick, ride.Route.Destination.Name, urlReady)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSWillCallDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate)
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailWillCallDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate)
		//messageDispatcherApp := fmt.Sprintf(MessageAppPendingDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		notifications = append(notifications, s.GetNotification(ride, "sms", MessageSMSWillCallMemberTitle, messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSWillCallDispatcherTitle, ride.User.Name), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", MessageEmailWillCallMemberTitle, messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailWillCallDispatcherTitle, ride.User.Name), messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", "Ride Scheduled "+strings.ToLower(ride.TripType.Value), fmt.Sprintf(MessageAppWillCallMemberTitle, ride.Status.Value, ride.Route.Destination.Address), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", "Ride Scheduled "+strings.ToLower(ride.TripType.Value), fmt.Sprintf(MessageAppWillCallDispatcherTitle, ride.Status.Value, ride.Route.Destination.Address), true, "ride"))
	case StatusScheduled:
		dateFormat := ride.PickupTime.In(loc).Format("01/02/2006 03:04 PM")
		visitTime := ride.VisitDate.In(loc).Format(hourMinuteAMPM)
		pickupTime := ride.PickupTime.In(loc).Format(hourMinuteAMPM)
		visitDate := ride.VisitDate.In(loc).Format("01/02/2006")

		if ride.TripType.Key == "from_visit" && lyftRide.ReturnTime != nil {
			dateFormat = lyftRide.ReturnTime.In(loc).Format("01/02/2006 03:04 PM")
			pickupTime = lyftRide.ReturnTime.In(loc).Format(hourMinuteAMPM)
		}

		messageMemberSMS := fmt.Sprintf(MessageSMSScheduledMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		messageMemberSMS += "\n\n" + MessageSMSReplyOrCancel
		messageMemberEmail := fmt.Sprintf(MessageEmailScheduledMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		//messageMemberApp := fmt.Sprintf(MessageAppPendingMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSScheduledDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailScheduledDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		//messageDispatcherApp := fmt.Sprintf(MessageAppPendingDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSScheduledMemberTitle, dateFormat), messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSScheduledDispatcherTitle, ride.User.Name, dateFormat), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailScheduledMemberTitle, dateFormat), messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailScheduledDispatcherTitle, ride.User.Name, dateFormat), messageDispatcherEmail, true, ""))

		// if ride.TripType.Key == "to_visit" {
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+strings.ToLower(ride.TripType.Value), ride.Status.Value), fmt.Sprintf(MessageAppScheduledMemberTitle, pickupTime, ride.Route.Origin.Name), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+strings.ToLower(ride.TripType.Value), ride.Status.Value), fmt.Sprintf(MessageAppScheduledDispatcherTitle, pickupTime, ride.Route.Origin.Name), true, "ride"))
		// } else if ride.TripType.Key == "from_visit" {
		// 	notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("Ride %s "+strings.ToLower(ride.TripType.Value), StatusScheduled), fmt.Sprintf(MessageAppScheduledMemberTitle, pickupTime, ride.Route.Destination.Name), false, "ride"))
		// 	notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("Ride %s "+strings.ToLower(ride.TripType.Value), StatusScheduled), fmt.Sprintf(MessageAppScheduledDispatcherTitle, pickupTime, ride.Route.Destination.Name), true, "ride"))
		// }
	case StatusPending:
		dateFormat := ride.PickupTime.In(loc).Format("01/02/2006 03:04 PM")
		visitTime := ride.VisitDate.In(loc).Format(hourMinuteAMPM)
		pickupTime := ride.PickupTime.In(loc).Format(hourMinuteAMPM)
		visitDate := ride.VisitDate.In(loc).Format("01/02/2006")

		messageMemberSMS := fmt.Sprintf(MessageSMSPendingMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		messageMemberSMS += "\n\n" + MessageSMSReplyOrCancel
		messageMemberEmail := fmt.Sprintf(MessageEmailPendingMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		//messageMemberApp := fmt.Sprintf(MessageAppPendingMember, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSPendingDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailPendingDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)
		//messageDispatcherApp := fmt.Sprintf(MessageAppPendingDispatcher, ride.User.Name, visitTime, ride.Route.Destination.Name, visitDate, ride.Route.Origin.Address, pickupTime)

		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSPendingMemberTitle, dateFormat), messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSPendingDispatcherTitle, ride.User.Name, dateFormat), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailPendingMemberTitle, dateFormat), messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailPendingDispatcherTitle, ride.User.Name, dateFormat), messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppPendingMemberTitle, pickupTime, ride.Route.Origin.Name), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppPendingDispatcherTitle, pickupTime, ride.Route.Origin.Name), true, "ride"))
	case StatusAccepted:
		pickupTime := ride.PickupTime.In(loc).Format(hourMinuteAMPM)

		messageMemberSMS := fmt.Sprintf(MessageSMSAcceptedMember, lyftRide.Driver.FirstName, ride.Route.Origin.Address, pickupTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		messageMemberSMS += "\n\n" + MessageSMSReplyOrCancel
		messageMemberEmail := fmt.Sprintf(MessageEmailAcceptedMember, lyftRide.Driver.FirstName, ride.Route.Origin.Address, pickupTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		//messageMemberApp := fmt.Sprintf(MessageAppAcceptedMember, lyftRide.Driver.FirstName, ride.Route.Origin.Address, visitTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSAcceptedDispatcher, lyftRide.Driver.FirstName, ride.Route.Origin.Address, pickupTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailAcceptedDispatcher, lyftRide.Driver.FirstName, ride.Route.Origin.Address, pickupTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		//messageDispatcherApp := fmt.Sprintf(MessageAppAcceptedDispatcher, lyftRide.Driver.FirstName, ride.Route.Origin.Address, visitTime, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)

		notifications = append(notifications, s.GetNotification(ride, "sms", MessageSMSAcceptedMemberTitle, messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSAcceptedDispatcherTitle, ride.User.Name), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", MessageEmailAcceptedMemberTitle, messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailAcceptedDispatcherTitle, ride.User.Name), messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppAcceptedMemberTitle, s.secondsToMinutes(*lyftRide.Origin.ETASeconds), ride.Route.Origin.Name), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppAcceptedDispatcherTitle, s.secondsToMinutes(*lyftRide.Origin.ETASeconds), ride.Route.Origin.Name), true, "ride"))
	case StatusArrived:
		messageMemberSMS := fmt.Sprintf(MessageSMSArrivedMember, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		messageMemberSMS += "\n\n" + MessageSMSReplyOrCancel
		messageMemberEmail := fmt.Sprintf(MessageEmailArrivedMember, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		//messageMemberApp := fmt.Sprintf(MessageAppArrivedMember, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSArrivedDispatcher, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailArrivedDispatcher, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)
		//messageDispatcherApp := fmt.Sprintf(MessageAppArrivedDispatcher, ride.Route.Origin.Address, lyftRide.Driver.FirstName, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate, url)

		notifications = append(notifications, s.GetNotification(ride, "sms", MessageSMSArrivedMemberTitle, messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSArrivedDispatcherTitle, ride.User.Name), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", MessageEmailArrivedMemberTitle, messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailArrivedDispatcherTitle, ride.User.Name), messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", "Your ride is HERE", fmt.Sprintf(MessageAppArrivedMemberTitle, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", "Waiting for member", fmt.Sprintf(MessageAppArrivedDispatcherTitle, lyftRide.Vehicle.Color, lyftRide.Vehicle.Make, lyftRide.Vehicle.Model, lyftRide.Vehicle.LicensePlate), true, "ride"))
	case StatusPickedUp:
		dateFormat := time.Now().In(loc).Format("01/02/2006 03:04 PM")
		visitTime := ride.VisitDate.In(loc).Format(hourMinuteAMPM)
		pickupTime := lyftRide.Pickup.Time.In(loc).Format(hourMinuteAMPM)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSPickedUpDispatcher, ride.User.Name, ride.Route.Origin.Address, dateFormat, url, visitTime, s.secondsToMinutes(*lyftRide.Destination.ETASeconds))
		messageDispatcherSMS += "\n\n" + MessageSMSReplyOrCancel
		messageDispatcherEmail := fmt.Sprintf(MessageEmailPickedUpDispatcher, ride.User.Name, ride.Route.Origin.Address, dateFormat, url, visitTime, s.secondsToMinutes(*lyftRide.Destination.ETASeconds))
		//messageDispatcherApp := fmt.Sprintf(MessageAppPickedUpDispatcher, ride.User.Name, ride.Route.Origin.Address, dateFormat, url, visitTime, s.secondsToMinutes(*lyftRide.Destination.ETASeconds))

		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSPickedUpDispatcherTitle, ride.User.Name), messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailPickedUpDispatcherTitle, ride.User.Name), messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppPickedUpDispatcherTitle, pickupTime), true, "ride"))
	case StatusDroppedOff:
		dateFormat := time.Now().In(loc).Format("01/02/2006 03:04 PM")
		dropOffTime := lyftRide.DropOff.Time.In(loc).Format(hourMinuteAMPM)

		// messageMemberSMS := MessageSMSDroppedOffMember
		// messageMemberEmail := MessageEmailDroppedOffMember

		// if ride.Visit.TripType.Key == "roundtrip_call" && ride.TripType.Key == "to_visit" {
		// 	entityRide, err := s.svc.Rides.GetByUUID(ride.UUID)
		// 	if err != nil {
		// 		fmt.Println(err)
		// 	}

		// 	var nextRide entity.Ride
		// 	for _, r := range entityRide.Visit.Rides {
		// 		fmt.Println("Ride Tryp Type: ", r.TripType.Key)
		// 		if r.TripType.Key == "from_visit_call" {
		// 			nextRide = r
		// 		}
		// 	}

		// 	fmt.Println("NextRide Found: ", nextRide.UUID)
		// 	if nextRide.UUID != "" {
		// 		urlReady, err := s.getReadyURLShortened(nextRide)
		// 		if err != nil {
		// 			fmt.Println(err)
		// 		}

		// 		messageMemberSMS += "\n" + fmt.Sprintf(MessageSMSIAmReadyOrClick, nextRide.Route.Destination.Name, urlReady)
		// 		messageMemberEmail += "\n" + fmt.Sprintf(MessageEmailIAmReadyOrClick, nextRide.Route.Destination.Name, urlReady)
		// 	}
		// }

		// fmt.Println("SMS Message: ", messageMemberSMS)

		messageDispatcherSMS := fmt.Sprintf(MessageSMSDroppedOffDispatcher, ride.User.Name, ride.Route.Destination.Name, dateFormat, url, ride.Visit.ExternalID)
		messageDispatcherEmail := fmt.Sprintf(MessageEmailDroppedOffDispatcher, ride.User.Name, ride.Route.Destination.Name, dateFormat, url, ride.Visit.ExternalID)
		//messageDispatcherApp := fmt.Sprintf(MessageAppDroppedOffDispatcher, ride.User.Name, ride.Route.Destination.Name, dateFormat, url, ride.Visit.ExternalID)

		//notifications = append(notifications, s.GetNotification(ride, "sms", MessageSMSDroppedOffMemberTitle, messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", fmt.Sprintf(MessageSMSDroppedOffDispatcherTitle, ride.User.Name), messageDispatcherSMS, true, ""))
		//notifications = append(notifications, s.GetNotification(ride, "email", MessageEmailDroppedOffMemberTitle, messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", fmt.Sprintf(MessageEmailDroppedOffDispatcherTitle, ride.User.Name), messageDispatcherEmail, true, ""))
		//notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf(MessageAppDroppedOffMemberTitle, ride.Route.Destination.Name), fmt.Sprintf(MessageAppDroppedOffMemberTitle, ride.Route.Destination.Name), false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), fmt.Sprintf(MessageAppDroppedOffDispatcherTitle, dropOffTime), true, "ride"))
	case StatusCanceled:
		visitTime := ride.VisitDate.Format(hourMinuteAMPM)
		visitDate := ride.VisitDate.Format("01/02/2006")

		var messageMemberSMS string
		var messageMemberSMSTitle string
		var messageMemberEmail string
		var messageMemberEmailTitle string
		//var messageMemberApp string
		var messageMemberAppTitle string

		var messageDispatcherSMS string
		var messageDispatcherSMSTitle string
		var messageDispatcherEmail string
		var messageDispatcherEmailTitle string
		//var messageDispatcherApp string
		var messageDispatcherAppTitle string

		if lyftRide.CanceledBy == "driver" {
			messageMemberSMS = fmt.Sprintf(MessageSMSCanceledByDriverMember, visitTime, ride.Visit.ExternalID)
			messageMemberSMSTitle = MessageSMSCanceledByDriverMemberTitle
			messageMemberEmail = fmt.Sprintf(MessageEmailCanceledByDriverMember, visitTime, ride.Visit.ExternalID)
			messageMemberEmailTitle = MessageEmailCanceledByDriverMemberTitle
			//messageMemberApp = fmt.Sprintf(MessageAppCanceledByDriverMember, visitTime, ride.Visit.ExternalID)
			messageMemberAppTitle = MessageAppCanceledByDriverMemberTitle

			messageDispatcherSMS = fmt.Sprintf(MessageSMSCanceledByDriverDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherSMSTitle = fmt.Sprintf(MessageSMSCanceledByDriverDispatcherTitle, ride.User.Name)
			messageDispatcherEmail = fmt.Sprintf(MessageEmailCanceledByDriverDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherEmailTitle = fmt.Sprintf(MessageEmailCanceledByDriverDispatcherTitle, ride.User.Name)
			//messageDispatcherApp = fmt.Sprintf(MessageAppCanceledByDriverDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherAppTitle = MessageAppCanceledByDriverDispatcherTitle
		} else if lyftRide.CanceledBy == "no_drivers_available" {
			messageMemberSMS = fmt.Sprintf(MessageSMSCanceledNoDriverMember, visitTime, ride.Route.Destination.Name, visitDate)
			messageMemberSMSTitle = MessageSMSCanceledNoDriverMemberTitle
			messageMemberEmail = fmt.Sprintf(MessageEmailCanceledNoDriverMember, visitTime, ride.Route.Destination.Name, visitDate)
			messageMemberEmailTitle = MessageEmailCanceledNoDriverMemberTitle
			//messageMemberApp = fmt.Sprintf(MessageAppCanceledNoDriverMember, visitTime, ride.Route.Destination.Name, visitDate)
			messageMemberAppTitle = MessageAppCanceledNoDriverMemberTitle

			messageDispatcherSMS = fmt.Sprintf(MessageSMSCanceledNoDriverDispatcher, visitTime, ride.Route.Destination.Name, visitDate, ride.Visit.ExternalID)
			messageDispatcherSMSTitle = fmt.Sprintf(MessageSMSCanceledNoDriverDispatcherTitle, ride.User.Name)
			messageDispatcherEmail = fmt.Sprintf(MessageEmailCanceledNoDriverDispatcher, visitTime, ride.Route.Destination.Name, visitDate, ride.Visit.ExternalID)
			messageDispatcherEmailTitle = fmt.Sprintf(MessageEmailCanceledNoDriverDispatcherTitle, ride.User.Name)
			//messageDispatcherApp = fmt.Sprintf(MessageAppCanceledNoDriverDispatcher, visitTime, ride.Route.Destination.Name, visitDate, ride.Visit.ExternalID)
			messageDispatcherAppTitle = MessageAppCanceledNoDriverDispatcherTitle
		} else {
			messageMemberSMS = fmt.Sprintf(MessageSMSCanceledMember, visitTime, ride.Visit.ExternalID)
			messageMemberSMSTitle = MessageSMSCanceledMemberTitle
			messageMemberEmail = fmt.Sprintf(MessageEmailCanceledMember, visitTime, ride.Visit.ExternalID)
			messageMemberEmailTitle = MessageEmailCanceledMemberTitle
			//messageMemberApp = fmt.Sprintf(MessageAppCanceledMember, visitTime, ride.Visit.ExternalID)
			messageMemberAppTitle = MessageAppCanceledMemberTitle

			messageDispatcherSMS = fmt.Sprintf(MessageSMSCanceledDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherSMSTitle = fmt.Sprintf(MessageSMSCanceledDispatcherTitle, ride.User.Name)
			messageDispatcherEmail = fmt.Sprintf(MessageEmailCanceledDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherEmailTitle = fmt.Sprintf(MessageEmailCanceledDispatcherTitle, ride.User.Name)
			//messageDispatcherApp = fmt.Sprintf(MessageAppCanceledDispatcher, visitTime, ride.Visit.ExternalID)
			messageDispatcherAppTitle = MessageAppCanceledDispatcherTitle //fmt.Sprintf(MessageAppCanceledDispatcherTitle, ride.User.Name)
		}

		notifications = append(notifications, s.GetNotification(ride, "sms", messageMemberSMSTitle, messageMemberSMS, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "sms", messageDispatcherSMSTitle, messageDispatcherSMS, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", messageMemberEmailTitle, messageMemberEmail, false, ""))
		notifications = append(notifications, s.GetNotification(ride, "email", messageDispatcherEmailTitle, messageDispatcherEmail, true, ""))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), messageMemberAppTitle, false, "ride"))
		notifications = append(notifications, s.GetNotification(ride, "app", fmt.Sprintf("%s | "+ride.TripType.Value, ride.Status.Value), messageDispatcherAppTitle, true, "ride"))
	}

	notifications, err = s.SendNotifications(notifications)
	if err != nil {
		fmt.Println("Error to notify")
		return err
	}

	return nil
}

func (s *notificationService) GetNotification(ride viewmodel.Ride, notificationType string, subject string, message string, isDispatcher bool, messageType string) viewmodel.Notification {
	retVal := viewmodel.Notification{
		Type:        notificationType,
		Message:     message,
		Subject:     subject,
		Ride:        ride,
		User:        ride.User,
		CreatedUser: ride.CreatedUser,
		Read:        false,
		MessageType: messageType,
	}

	if isDispatcher {
		retVal.User = ride.CreatedUser
		retVal.CreatedUser = ride.User
	}

	switch notificationType {
	case NotificationTypeEmail:
		if isDispatcher {
			retVal.To = *ride.CreatedUser.Email
		} else {
			retVal.To = *ride.User.Email
		}
	case NOtificationTypeSMS:
		if isDispatcher {
			retVal.To = *ride.CreatedUser.PhoneNumber
		} else {
			retVal.To = *ride.User.PhoneNumber
		}
	case NotificationTypeAPP:
		if isDispatcher {
			retVal.To = ride.CreatedUser.ID
		} else {
			retVal.To = ride.User.ID
		}
	}

	return retVal
}

func (s *notificationService) SendMessage(message viewmodel.Message) error {
	bMessage, err := ffjson.Marshal(message)
	if err != nil {
		return err
	}

	return s.store.redisClient.Publish(message.DeliveryID, string(bMessage)).Err()
}

func (s *notificationService) GetByUserUUIDAndReadStatus(user viewmodel.User, contactType string, isRead bool) ([]viewmodel.Notification, error) {
	n, err := s.svc.Notification.GetByUserUUIDAndReadStatus(user.ID, contactType, isRead)
	if err != nil {
		return nil, err
	}

	authUser, err := s.svc.Users.GetByUUID(user.ID, "")
	if err != nil {
		return nil, err
	}

	if !isRead {
		for i, _ := range n {
			r, err := s.svc.Rides.GetByID(n[i].Ride.ID, authUser)
			if err != nil {
				return nil, err
			}

			n[i].Ride = r
		}
	}

	return s.mapEntity.Notification.ToNotificationModelSlice(n), nil
}

func (s *notificationService) Subscribe(user viewmodel.User, conn *websocket.Conn) (*UserNotification, error) {
	return s.store.Subscribe(user, conn)
}

func (s *notificationService) DeliverMessage() {
	for {
		v, _ := s.pubSub.Receive()
		switch t := v.(type) {
		case *redis.Message:
			var m viewmodel.Message
			ffjson.Unmarshal([]byte(t.Payload), &m)
			s.store.FindAndDeliver(m)
		case error:
			fmt.Println("Error to delivery messages: ", t.Error())
			return
		default:
			if t != nil {
				fmt.Println("Unknown Event: ", t)
				fmt.Println("Type: ", reflect.TypeOf(t))
			}
		}
	}
}

//FindAndDeliver will send a message to an open channel
func (s *Store) FindAndDeliver(m viewmodel.Message) {
	if s.Users[m.DeliveryID] != nil {
		if s.Users[m.DeliveryID].ID != "" {
			if len(s.Users[m.DeliveryID].Conn) > 0 {
				for i, _ := range s.Users[m.DeliveryID].Conn {
					if s.Users[m.DeliveryID].Conn[i] != nil {
						if err := s.Users[m.DeliveryID].Conn[i].WriteJSON(m); err != nil {
							s.Users[m.DeliveryID].Conn[i] = nil
						}
					}
				}
			}
			return
		}
	}
}

//Subscribe add the user to a subscription
func (s *Store) Subscribe(user viewmodel.User, conn *websocket.Conn) (*UserNotification, error) {
	u := s.Users[user.ID]
	if u == nil {
		u := &UserNotification{
			ID:   user.ID,
			Conn: []*websocket.Conn{conn},
			User: user,
		}
		s.Users[u.ID] = u
	} else {
		u.Conn = append(u.Conn, conn)
		s.Users[u.ID] = u
	}

	err := s.pubSub.Subscribe(user.ID)
	if err != nil {
		return nil, err
	}
	s.Lock()
	defer s.Unlock()

	return u, nil
}

func (s *notificationService) ReadStatus(notificationUUID string, isRead bool) error {
	return s.svc.Notification.ReadStatus(notificationUUID, isRead)
}

//SendNotificationWithoutWritingToDatabase will send notification directly
func (s *notificationService) SendNotificationWithoutWritingToDatabase(n viewmodel.Notification) (viewmodel.Notification, error) {
	switch n.Type {
	case NOtificationTypeSMS:
		if n.From == "" {
			if err := s.notification.Twilio.SendSMS(s.cfg.Twilio.Sender, n.To, n.Message); err != nil {
				fmt.Println("Error to send SMS: ", err.Error())
				return viewmodel.Notification{}, err
			}
			if err := s.notification.Twilio.SendSMS(s.cfg.Twilio.Sender, "+17083038497", n.Message); err != nil {
				fmt.Println("Error to send SMS: ", err.Error())
				return viewmodel.Notification{}, err
			}
		} else {
			if err := s.notification.Twilio.SendSMS(n.From, n.To, n.Message); err != nil {
				fmt.Println("Error to send SMS: ", err.Error())
				return viewmodel.Notification{}, err
			}
			if err := s.notification.Twilio.SendSMS(n.From, "+17083038497", n.Message); err != nil {
				fmt.Println("Error to send SMS: ", err.Error())
				return viewmodel.Notification{}, err
			}
		}
	case NotificationTypeEmail:
		m := gomail.NewMessage()
		m.SetHeader("From", s.cfg.Email.Sender)
		m.SetHeader("To", n.To)
		m.SetHeader("Subject", n.Subject)
		m.SetBody("text/plain", n.Message)
		d := gomail.NewDialer(s.cfg.Email.Server, s.cfg.Email.Port, s.cfg.Email.User, s.cfg.Email.Pass)

		if err := d.DialAndSend(m); err != nil {
			fmt.Println("Error to send Email: ", err.Error())
			return viewmodel.Notification{}, err
		}

		m = gomail.NewMessage()
		m.SetHeader("From", s.cfg.Email.Sender)
		m.SetHeader("To", "nemt@brighterdevelopment.com")
		m.SetHeader("Subject", n.Subject)
		m.SetBody("text/plain", n.Message)

		if err := d.DialAndSend(m); err != nil {
			fmt.Println("Error to send Email: ", err.Error())
			return viewmodel.Notification{}, err
		}
	}
	return n, nil
}

// SendNotifications will send all the notifications to email or SMS
func (s *notificationService) SendNotifications(notifications []viewmodel.Notification) ([]viewmodel.Notification, error) {
	if len(notifications) > 0 {
		retVal := make([]viewmodel.Notification, 0)
		for _, n := range notifications {
			if n.To != "" {
				notification := s.mapEntity.Notification.ToNotificationEntity(n)
				notification, err := s.svc.Notification.Create(notification)
				if err != nil {
					return nil, err
				}

				switch n.Type {
				case NOtificationTypeSMS:
					if n.From == "" {
						if err := s.notification.Twilio.SendSMS(s.cfg.Twilio.Sender, n.To, n.Message); err != nil {
							fmt.Println("Error to send SMS: ", err.Error())
						}
						if err := s.notification.Twilio.SendSMS(s.cfg.Twilio.Sender, "+17083038497", n.Message); err != nil {
							fmt.Println("Error to send SMS: ", err.Error())
						}
					} else {
						if err := s.notification.Twilio.SendSMS(n.From, n.To, n.Message); err != nil {
							fmt.Println("Error to send SMS: ", err.Error())
						}
						if err := s.notification.Twilio.SendSMS(n.From, "+17083038497", n.Message); err != nil {
							fmt.Println("Error to send SMS: ", err.Error())
						}
					}
				case NotificationTypeEmail:
					m := gomail.NewMessage()
					m.SetHeader("From", s.cfg.Email.Sender)
					m.SetHeader("To", n.To)
					m.SetHeader("Subject", n.Subject)
					m.SetBody("text/plain", n.Message)
					d := gomail.NewDialer(s.cfg.Email.Server, s.cfg.Email.Port, s.cfg.Email.User, s.cfg.Email.Pass)

					if err := d.DialAndSend(m); err != nil {
						fmt.Println("Error to send Email: ", err.Error())
					}

					m = gomail.NewMessage()
					m.SetHeader("From", s.cfg.Email.Sender)
					m.SetHeader("To", "nemt@brighterdevelopment.com")
					m.SetHeader("Subject", n.Subject)
					m.SetBody("text/plain", n.Message)

					if err := d.DialAndSend(m); err != nil {
						fmt.Println("Error to send Email: ", err.Error())
					}
				case NotificationTypeAPP:
					m := viewmodel.Message{
						DeliveryID:     n.To,
						NotificationID: notification.UUID,
						CreateDate:     notification.Created,
						Read:           notification.Read,
						Content: viewmodel.MessageContent{
							Type:    n.MessageType,
							Subject: n.Subject,
							Content: n.Message,
							Payload: n.Ride,
						},
					}

					if err := s.SendMessage(m); err != nil {
						fmt.Println("Error to send notification to the users: ", err.Error())
					}
				}
				retVal = append(retVal, s.mapEntity.Notification.ToNotificationModel(notification))
			}
		}
		return retVal, nil
	}
	return notifications, nil
}

type UserNotification struct {
	ID   string
	Conn []*websocket.Conn
	User viewmodel.User
}

type Store struct {
	Users       map[string]*UserNotification
	redisClient *redis.Client
	pubSub      *redis.PubSub
	sync.Mutex
}
