package visitroute

import (
	"fmt"
	"math/rand"
	"strings"
	"sync"
	"time"

	"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi"
	"bitbucket.org/nemt/nemt-portal-api/application/tncservice"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/infra/auth"
	"bitbucket.org/nemt/nemt-portal-api/infra/config"
	"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
	"bitbucket.org/nemt/nemt-portal-api/server/validation"
	"github.com/labstack/echo"
	uuid "github.com/satori/go.uuid"
)

var (
	instance *controller
	once     sync.Once
)

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

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

func (c *controller) generatePassword(n int) string {
	const (
		charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	)

	return c.stringWithCharset(n, charset)
}

func (c *controller) stringWithCharset(length int, charset string) string {
	b := make([]byte, length)
	var seededRand *rand.Rand = rand.New(
		rand.NewSource(time.Now().UnixNano()))
	for i := range b {
		b[i] = charset[seededRand.Intn(len(charset))]
	}
	return string(b)
}

func (c *controller) handleRide(ctx echo.Context) error {
	var ride viewmodel.RideRequest
	if err := ctx.Bind(&ride); err != nil {
		fmt.Println(err)
		return routeutils.ResponseAPIValidationError(ctx, "invalid parameters")
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	visit, err := c.svc.Visits.GetByUUID(ride.Visit.UUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	visit.User.PhoneNumber = ride.User.PhoneNumber
	visit.User.Email = ride.User.Email
	visit.User.Consent = true

	ride.Visit = visit

	var provider viewmodel.ProviderResp
	provider, err = c.svc.Provider.GetByUUID(ride.Visit.Provider.ProviderUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}
	ride.Visit.Provider = provider

	user, err := c.svc.Users.CheckAndCreateMember(ride.Visit.User, provider, authUser)
	if err != nil {
		if validationError, ok := err.(*viewmodel.ValidationError); ok {
			if len(validationError.Errors) > 0 {
				return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
			} else {
				return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
			}
		} else {
			return routeutils.HandleAPIError(ctx, err)
		}
	}
	ride.Visit.User = user
	ride.User = user

	homeAddress := viewmodel.Address{}
	for _, a := range visit.User.Addresses {
		if a.AddressType == "home" {
			homeAddress = a
		}
	}

	ride.CreateUserUUID = authUser.ID
	ride.Destination = viewmodel.Location{
		ID:        visit.Provider.ProviderUUID,
		Name:      visit.Provider.OrganizatioName,
		Latitude:  visit.Provider.Address.Latitude,
		Longitude: visit.Provider.Address.Longitude,
		Address:   visit.Provider.Address.StreetAddress1,
	}

	if len(ride.Origin.ID) == 0 {
		ride.Origin = viewmodel.Location{
			ID:        homeAddress.UUID,
			Name:      homeAddress.AddressTypeName,
			Latitude:  homeAddress.Latitude,
			Longitude: homeAddress.Longitude,
			Address:   homeAddress.Address,
		}
	}

	ride.Notes = ride.Notes
	ride.Passenger.FirstName = visit.User.First
	ride.Passenger.LastName = " "
	ride.Passenger.PhoneNumber = *visit.User.PhoneNumber
	ride.RideType = "lyft"
	ride.VisitDate = &visit.VisitDatetime
	ride.VisitTime = &visit.VisitDatetime
	ride.UserConsent = true

	if validationErrors := validation.ValidateVisitRide(&ride, &user); len(validationErrors) > 0 {
		return routeutils.ResponseAPICustomValidationError(ctx, "ride validation failed", validationErrors)
	}

	var resp viewmodel.RideRequest
	if ride.TripType.Key != "from_visit_call" {
		if ride.TripType.Key == "from_visit" {
			newOrigin := ride.Origin
			ride.Origin = ride.Destination
			ride.Destination = newOrigin
		}

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

		if ride.TripType.Key == "from_visit" {
			newOrigin := resp.Origin
			resp.Origin = resp.Destination
			resp.Destination = newOrigin
		}

		resp.RideID = strings.Replace(resp.RideID, "s_", "", -1)
	} else {
		resp = ride

		UUID, _ := uuid.NewV4()
		resp.RideID = UUID.String()
	}

	if resp.Status == "scheduled" || ride.TripType.Key == "from_visit_call" {
		if ride.PickupTime == nil {
			currentDate := time.Now()
			ride.PickupTime = &currentDate
		}
		requestMS := (ride.PickupTime.UnixNano() / int64(time.Millisecond))
		generateDate := time.Now()
		generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
		resp.RequestAt = ride.PickupTime
		resp.RequestAtMS = &requestMS
		resp.GeneratedAt = &generateDate
		resp.GeneratedAtMS = &generateMS
	}

	resp.Passenger.FirstName = visit.User.First
	resp.Passenger.LastName = visit.User.Last
	resp.Passenger.PhoneNumber = *visit.User.PhoneNumber
	if resp.Passenger.ImageURL == nil {
		imageURL := " "
		resp.Passenger.ImageURL = &imageURL
	}
	resp.UserUUID = visit.User.ID

	if ride.TripType.Key == "from_visit" {
		resp.Origin.Name = ride.Destination.Name
		resp.Origin.ID = ride.Destination.ID
		resp.Destination.Name = ride.Origin.Name
		resp.Destination.ID = ride.Origin.ID
	} else {
		resp.Origin.Name = ride.Origin.Name
		resp.Origin.ID = ride.Origin.ID
		resp.Destination.Name = ride.Destination.Name
		resp.Destination.ID = ride.Destination.ID
	}

	resp.Distance = ride.Distance
	resp.Duration = ride.Duration
	resp.ETA = ride.ETA
	resp.PickupTime = ride.PickupTime
	resp.VisitDate = &ride.Visit.VisitDatetime
	resp.VisitTime = &ride.Visit.VisitDatetime
	resp.VisitExternalID = ride.Visit.ExternalID
	resp.CreateUserUUID = authUser.ID
	resp.Visit.TripType = ride.Visit.TripType
	resp.Visit = visit

	if resp.TripType.Key == "from_visit_call" {
		resp.Status = "willCall"
		resp.Passenger.UserID = &resp.UserUUID
	}

	resp.TripType.Key = ride.TripType.Key
	if ride.TripType.Key == "roundtrip" || ride.TripType.Key == "roundtrip_call" {
		resp.TripType.Key = "to_visit"
	} else if ride.TripType.Key == "from_visit_call" {
		resp.TripType.Key = "from_visit_call"
	}

	entity, err := c.svc.Rides.Save(resp)
	if err != nil {
		fmt.Println("Error to save first ride: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

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

	if ride.TripType.Key == "roundtrip" || ride.TripType.Key == "roundtrip_call" {
		newRide := ride

		if ride.TripType.Key == "roundtrip" {
			destination := newRide.Origin
			newRide.Origin = newRide.Destination
			newRide.Destination = destination
			if ride.ReturnTime != nil {
				newRide.PickupTime = ride.ReturnTime
			}

			scheduledRide := make(map[string]interface{})
			scheduledRide["timestamp_ms"] = ride.PickupTime.Unix()
			newRide.ScheduledPickupRange = scheduledRide

			newRide.Passenger.FirstName = visit.User.First
			newRide.Passenger.LastName = " "
			newRide.Passenger.PhoneNumber = *visit.User.PhoneNumber

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

			destination = newRide.Origin
			newRide.Origin = newRide.Destination
			newRide.Destination = destination

			if newRide.Error != "" {
				fmt.Println("Error to schedule a ride on lyft: ", newRide.Error, newRide.ErrorDescription)
			} else {
				fmt.Println("Ride Scheduled: ", newRide.Status)
			}

			newRide.TripType.Key = "from_visit"
		} else {
			newRide.TripType.Key = "from_visit_call"
			newRide.Status = "willCall"
			newRide.RideID = entity.UUID
		}

		newRide.Visit = visit
		newRide.PickupTime = ride.ReturnTime

		requestMS := (newRide.PickupTime.UnixNano() / int64(time.Millisecond))
		generateDate := time.Now()
		generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
		newRide.RequestAt = newRide.PickupTime
		newRide.RequestAtMS = &requestMS
		newRide.GeneratedAt = &generateDate
		newRide.GeneratedAtMS = &generateMS

		newRide.Passenger.FirstName = visit.User.First
		newRide.Passenger.LastName = visit.User.Last
		newRide.Passenger.PhoneNumber = *visit.User.PhoneNumber
		newRide.Passenger.UserID = &ride.UserUUID

		if newRide.Passenger.ImageURL == nil {
			imageURL := " "
			newRide.Passenger.ImageURL = &imageURL
		}

		newRide.UserUUID = ride.UserUUID
		newRide.Origin.Name = ride.Destination.Name
		newRide.Origin.ID = ride.Destination.ID
		newRide.Destination.Name = ride.Origin.Name
		newRide.Destination.ID = ride.Origin.ID
		newRide.Distance = ride.Distance
		newRide.Duration = ride.Duration
		newRide.ETA = ride.ETA
		newRide.VisitDate = ride.VisitDate
		newRide.VisitTime = ride.VisitTime
		newRide.VisitExternalID = ride.VisitExternalID
		newRide.CreateUserUUID = authUser.ID

		roudtripRide, err := c.svc.Rides.Save(newRide)
		if err != nil {
			return routeutils.HandleAPIError(ctx, err)
		}

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

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

func (c *controller) rangeIn(low, hi int) string {
	result := low + rand.Intn(hi-low)
	return fmt.Sprintf("%v", result)
}

func (c *controller) handle(ctx echo.Context) error {
	var visit viewmodel.Visit
	if err := ctx.Bind(&visit); err != nil {
		fmt.Println(err)
		return routeutils.ResponseAPIValidationError(ctx, "invalid parameters")
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	var provider viewmodel.ProviderResp
	provider, err = c.svc.Provider.GetByNPI(visit.RawProvider.FivePartKeyGroups[0].ProviderNum, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if provider.ProviderUUID == "" {
		org := viewmodel.Organization{
			Type: viewmodel.OrganizationType{
				Key:  "provider",
				Name: "Provider",
			},
			Name:        visit.RawProvider.OrgName,
			Description: visit.RawProvider.OrgName,
			Main:        false,
			Author:      authUser,
			LastEditor:  authUser,
			Reference:   visit.RawProvider,
		}

		org, err = c.svc.Organization.AddOrganization(org, authUser)
		if err != nil {
			return routeutils.HandleAPIError(ctx, err)
		}

		provider, err = c.svc.Provider.GetByOrganization(org.UUID, authUser)
		if err != nil {
			return routeutils.HandleAPIError(ctx, err)
		}
	}
	visit.Provider = provider

	if validationErrors := validation.ValidateVisit(&visit, &authUser); len(validationErrors) > 0 {
		return routeutils.ResponseAPICustomValidationError(ctx, "visit validation failed", validationErrors)
	}

	user, err := c.svc.Users.CheckAndCreateMember(visit.User, provider, authUser)
	if err != nil {
		if validationError, ok := err.(*viewmodel.ValidationError); ok {
			if len(validationError.Errors) > 0 {
				return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
			} else {
				return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
			}
		} else {
			return routeutils.HandleAPIError(ctx, err)
		}
	}
	visit.User = user

	visit.TripType = viewmodel.TripType{
		Key:   "no_trip",
		Value: "No Trip",
	}

	visit.User = user
	visit.CreatedUser = authUser

	visit, err = c.svc.Visits.Save(visit)
	if err != nil {
		fmt.Println("Error saving visit: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	if authUser.Profiles[0].Key == "VIRPT" {
		return routeutils.ResponseNoContent(ctx, nil)
	} else {
		return routeutils.ResponseAPIOK(ctx, visit)
	}
}

func (c *controller) handleGetByID(ctx echo.Context) error {
	visit_uuid := ctx.Param("visit_uuid")
	if visit_uuid == "" {
		return routeutils.ResponseAPIValidationError(ctx, "visit_uuid param is mandatory")
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Visits.GetByUUID(visit_uuid, authUser)
	if err != nil {
		fmt.Println("Error to get Visit: ", err.Error())
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleGetAll(ctx echo.Context) error {
	user, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Visits.GetAll(user)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}
