package application

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
	groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
	groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
	mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
	solidarity_service "git.coopgo.io/sbouaram/solidarity-service/servers/grpc/proto"
	models "git.coopgo.io/coopgo-apps/parcoursmob/utils/models"

	"github.com/google/uuid"
	"github.com/gorilla/mux"
	geojson "github.com/paulmach/go.geojson"

	//"gitlab.scity.coop/maas/navitia-golang"
	//"gitlab.scity.coop/maas/navitia-golang/types"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/structpb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var Depart any
var Arrive any


var SolidarityData models.SolidarityTransport


func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	var (
		//journeys_results *navitia.JourneyResults
		carpool_results  any
		vehicle_results  []any
		solidarity_results  models.SolidarityTransport
	)
	vehiclech := make(chan []any, 1)
	//navitiaCh := make(chan *navitia.JourneyResults, 1)
	carpoolCh := make(chan any, 1)
	solidarityCh := make(chan models.SolidarityTransport, 1)

	locTime, errTime := time.LoadLocation("Europe/Paris")
	if errTime != nil {
		fmt.Println("Loading timezone location Europe/Paris error : ")
		fmt.Println("Missing zones in container ? ")
		panic(errTime)
	}

	departuredate := r.FormValue("departuredate")
	departuretime := r.FormValue("departuretime")
	departuredatetime, _ := time.ParseInLocation("2006-01-02 15:04", fmt.Sprintf("%s %s", departuredate, departuretime), locTime)

	departure := r.FormValue("departure")
	destination := r.FormValue("destination")

	searched := false

	var (
		departuregeo   *geojson.Feature
		destinationgeo *geojson.Feature
		//journeys       *navitia.JourneyResults
		carpoolresults any
		vehicles       = []any{}
		drivers        []any
		accounts 	   []any
	)

	if departuredate != "" && departuretime != "" && departure != "" && destination != "" {
		searched = true

		var err error

		departuregeo, err = geojson.UnmarshalFeature([]byte(departure))
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		destinationgeo, err = geojson.UnmarshalFeature([]byte(destination))
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// journeysRequest := func() {
		// 	//TODO make it a library
		// 	session, _ := navitia.NewCustom(
		// 		h.config.GetString("services.navitia.api_key"),
		// 		"https://api.navitia.io/v1",
		// 		&http.Client{})
		// 	if err != nil {
		// 		fmt.Println(err)
		// 		w.WriteHeader(http.StatusBadRequest)
		// 		navitiaCh <- nil
		// 		return
		// 	}

		// 	request := navitia.JourneyRequest{
		// 		From:          types.ID(fmt.Sprintf("%f", departuregeo.Geometry.Point[0]) + ";" + fmt.Sprintf("%f", departuregeo.Geometry.Point[1])),
		// 		To:            types.ID(fmt.Sprintf("%f", destinationgeo.Geometry.Point[0]) + ";" + fmt.Sprintf("%f", destinationgeo.Geometry.Point[1])),
		// 		Date:          departuredatetime.Add(-2 * time.Hour),
		// 		DateIsArrival: false, //TODO
		// 	}

		// 	journeys, err = session.Journeys(context.Background(), request)
		// 	if err != nil {
		// 		fmt.Println(err)
		// 		// w.WriteHeader(http.StatusBadRequest)
		// 		// return
		// 	}
		// 	navitiaCh <- journeys

		// }
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//CARPOOL
		carpoolRequest := func() {
			carpoolrequest := "https://api.rdex.ridygo.fr/journeys.json"

			client := &http.Client{}
			req, err := http.NewRequest("GET", carpoolrequest, nil)
			if err != nil {
				fmt.Println(err)
			}

			req.URL.RawQuery = fmt.Sprintf(
				"p[driver][state]=1&frequency=punctual&p[passenger][state]=0&p[from][latitude]=%f&p[from][longitude]=%f&p[to][latitude]=%f&p[to][longitude]=%f&p[outward][mindate]=%s&p[outward][maxdate]=%s",
				departuregeo.Geometry.Point[1], departuregeo.Geometry.Point[0],
				destinationgeo.Geometry.Point[1], destinationgeo.Geometry.Point[0],
				departuredatetime.Format("2006-01-02"), departuredatetime.Format("2006-01-02"))

			req.Header.Set("X-API-KEY", "123456")
			resp, err := client.Do(req)
			if err != nil {
				fmt.Println(err)
			}

			if err == nil && resp.StatusCode == http.StatusOK {
				err = json.NewDecoder(resp.Body).Decode(&carpoolresults)
				if err != nil {
					fmt.Println(err)
				}

				if carpoolresults == nil {
					carpoolresults = []any{}
				}
			} else {
				carpoolresults = []any{}
			}
			carpoolCh <- carpoolresults
		}
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// Vehicles
		vehicleRequest := func() {
			vehiclerequest := &fleets.GetVehiclesRequest{
				Namespaces: []string{"parcoursmob"},
			}
			vehicleresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), vehiclerequest)
			if err != nil {
				fmt.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
			}
			for _, vehicle := range vehicleresp.Vehicles {
				v := vehicle.ToStorageType()
				if v.Free(departuredatetime.Add(-24*time.Hour), departuredatetime.Add(168*time.Hour)) {
					vehicles = append(vehicles, v)
				}
			}
			vehiclech <- vehicles
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////:::
		//solidarity-service
		solidarityRequest := func() {
		timestamp := timestamppb.New(departuredatetime)
		PassengerId := r.FormValue("passenger_id")
		driverId := r.FormValue("driver_id")
		id := uuid.New().String()

		type Booking struct {
			bookingData  solidarity_service.CreateBookingSolidarityRequest 
		} 
		var BookingData Booking
		
		accountsRequest, err := h.services.GRPC.SolidarityService.GetAllPassengers(context.TODO(), &emptypb.Empty{})
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusBadRequest)
		}

		for _, o := range accountsRequest.Passenger {
				accounts = append(accounts, o)
		}

		cacheid := uuid.NewString()
		h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)


		request := solidarity_service.DriverJourneysRequest{
			DepartureDate: timestamp,
			Departure: &solidarity_service.Feature{
				Lat:     departuregeo.Geometry.Point[0],
				Long:    departuregeo.Geometry.Point[1],
				Address: departure,
			},
		}

		driversRequest, err := h.services.GRPC.SolidarityService.DriverJourneys(context.TODO(), &request)

		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		for _, o := range driversRequest.DriverJourneys {
				drivers = append(drivers, o)
		}

		if r.Method == "POST" {

			BookingData.bookingData = solidarity_service.CreateBookingSolidarityRequest {
				Booking : &solidarity_service.BookingSolidarityRequest {
					Id: id,
					DriverId: driverId,
					PassengerId: PassengerId,
					DepartureAddress: &solidarity_service.Feature{
						Lat:     departuregeo.Geometry.Point[0],
						Long:    departuregeo.Geometry.Point[1],
						Address: departure,
					},
					DestinationAddress: &solidarity_service.Feature{
						Lat:     destinationgeo.Geometry.Point[0],
						Long:    destinationgeo.Geometry.Point[1],
						Address: departure,
					},
					PickupDate : timestamp,
					
				},
			}
			
			BookingData.bookingData.Booking.DriverId = driverId
			BookingData.bookingData.Booking.Id = id

			booking, err := h.services.GRPC.SolidarityService.CreateBookingSolidarity(context.TODO(), &BookingData.bookingData)

			if err != nil {
				fmt.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			booking = booking

			SolidarityData = models.SolidarityTransport{
				Drivers: drivers,
				Beneficiaries: accounts,
			}

		}
		SolidarityData = models.SolidarityTransport{
			Drivers: drivers,
			Beneficiaries: accounts,
		}

		solidarityCh <- SolidarityData

		}
		//go journeysRequest()
		go carpoolRequest()
		go vehicleRequest()
		go solidarityRequest()
		carpool_results = <-carpoolCh
		//journeys_results = <-navitiaCh
		vehicle_results = <-vehiclech
		solidarity_results = <-solidarityCh

	}
	

	h.Renderer.JourneysSearch(w, r, carpool_results, vehicle_results, searched, departuregeo, destinationgeo, departuredate, departuretime, solidarity_results) //journeys_results,
}

type GroupsModule []groupstorage.Group

func (a GroupsModule) Len() int { return len(a) }
func (a GroupsModule) Less(i, j int) bool {
	return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
}
func (a GroupsModule) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func (h *ApplicationHandler) GroupsGestion(w http.ResponseWriter, r *http.Request) {

	request := &groupsmanagement.GetGroupsRequest{
		Namespaces: []string{"parcoursmob_groups_covoiturage"},
	}

	resp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), request)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var groups = []groupstorage.Group{}

	for _, group := range resp.Groups {
		g := group.ToStorageType()
		groups = append(groups, g)
	}

	cacheid := uuid.NewString()

	sort.Sort(GroupsModule(groups))
	h.cache.PutWithTTL(cacheid, groups, 1*time.Hour)

	h.Renderer.GroupsGestion(w, r, groups, cacheid)
}
func filterAcc(r *http.Request, a *mobilityaccounts.Account) bool {
	searchFilter, ok := r.URL.Query()["search"]

	if ok && len(searchFilter[0]) > 0 {
		name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
		if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) {
			return false
		}
	}

	return true
}

func (h *ApplicationHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {

	var beneficiary any
	var (
		departurgeo   *geojson.Feature
		dstinationgeo *geojson.Feature
	)
	searched := false

	if r.FormValue("beneficiaryid") != "" {

		searched = true

		requestbeneficiary := &mobilityaccounts.GetAccountRequest{
			Id: r.FormValue("beneficiaryid"),
		}

		respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		beneficiary = respbeneficiary.Account.ToStorageType()

		if r.Method == "POST" {
			departure := r.FormValue("departure")
			destination := r.FormValue("destination")

			if departure != "" && destination != "" {

				var err error

				departurgeo, err = geojson.UnmarshalFeature([]byte(departure))
				if err != nil {
					fmt.Println(err)
					w.WriteHeader(http.StatusBadRequest)
					return
				}

				dstinationgeo, err = geojson.UnmarshalFeature([]byte(destination))
				if err != nil {
					fmt.Println(err)
					w.WriteHeader(http.StatusBadRequest)
					return
				}
			}
			if r.FormValue("departure") != "" {
				var a any
				json.Unmarshal([]byte(r.FormValue("departure")), &a)

				Depart = a
			}
			if r.FormValue("destination") != "" {
				var a any
				json.Unmarshal([]byte(r.FormValue("destination")), &a)

				Arrive = a
			}
			r.ParseForm()

			if r.FormValue("name") == "" {

				fmt.Println("invalid name")
				w.WriteHeader(http.StatusBadRequest)
				return
			}

			if r.FormValue("number") == "" {

				fmt.Println("invalid number of personne")
				w.WriteHeader(http.StatusBadRequest)
				return
			}

			planDays := map[string]any{
				"lundi":    r.FormValue("lundi") == "on",
				"mardi":    r.FormValue("mardi") == "on",
				"mercredi": r.FormValue("mercredi") == "on",
				"jeudi":    r.FormValue("jeudi") == "on",
				"vendredi": r.FormValue("vendredi") == "on",
				"samedi":   r.FormValue("samedi") == "on",
				"dimanche": r.FormValue("dimanche") == "on",
			}

			groupidd := uuid.NewString()

			dataMap := map[string]any{
				"name":              r.FormValue("name"),
				"number":            r.FormValue("number"),
				"driver_first_name": respbeneficiary.Account.ToStorageType().Data["first_name"],
				"driver_last_name":  respbeneficiary.Account.ToStorageType().Data["last_name"],
				"depart":            Depart,
				"arrive":            Arrive,
				"departdate":        r.FormValue("departdate"),
				"date":              r.FormValue("date"),
				"enddate":           r.FormValue("enddate"),
				"departtime":        r.FormValue("departtime"),
				"time":              r.FormValue("time"),

				"planDays":  planDays,
				"recurrent": r.FormValue("recurrent"),
				"pontuelle": r.FormValue("ponctuelle"),
			}

			data, err := structpb.NewValue(dataMap)
			if err != nil {
				fmt.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			request_organization := &groupsmanagement.AddGroupRequest{
				Group: &groupsmanagement.Group{
					Id:        groupidd,
					Namespace: "parcoursmob_groups_covoiturage",
					Data:      data.GetStructValue(),
				},
			}

			_, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_organization)
			if err != nil {
				fmt.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", request_organization.Group.ToStorageType().ID), http.StatusFound)

			return
		}
	}
	accountsBeneficaire, err := h.beneficiaries(r)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Renderer.CreateGroup(w, r, Depart, Arrive, searched, beneficiary, accountsBeneficaire, departurgeo, dstinationgeo)

}

func (h *ApplicationHandler) DisplayGroupCovoiturage(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	groupid := vars["groupid"]

	request := &groupsmanagement.GetGroupRequest{
		Id: groupid,
	}

	resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var accounts = []any{}

	requesst := &mobilityaccounts.GetAccountsBatchRequest{
		Accountids: resp.Group.Members,
	}

	ressp, _ := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), requesst)

	for _, account := range ressp.Accounts {
		if filterAcc(r, account) {
			a := account.ToStorageType()
			accounts = append(accounts, a)
		}
	}

	cacheid := uuid.NewString()
	h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
	r.ParseForm()

	var beneficiary any
	searched := false

	if r.FormValue("beneficiaryid") != "" {

		searched = true

		requestbeneficiary := &mobilityaccounts.GetAccountRequest{
			Id: r.FormValue("beneficiaryid"),
		}

		respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		beneficiary = respbeneficiary.Account.ToStorageType()

		subscribe := &groupsmanagement.SubscribeRequest{
			Groupid:  resp.Group.ToStorageType().ID,
			Memberid: respbeneficiary.Account.Id,
		}

		_, err = h.services.GRPC.GroupsManagement.Subscribe(context.TODO(), subscribe)

		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		/*******************Code to store more information about mermbers groupscovoiturage**************/
		if r.FormValue("departure") != "" {
			var a any
			json.Unmarshal([]byte(r.FormValue("departure")), &a)

			Depart = a
		}
		if r.FormValue("destination") != "" {
			var a any
			json.Unmarshal([]byte(r.FormValue("destination")), &a)

			Arrive = a
		}
		r.ParseForm()
		dataMap := map[string]any{

			"depart": Depart,
			"arrive": Arrive,
		}
		id := uuid.NewString()
		data, err := structpb.NewValue(dataMap)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		request_organizatio := &groupsmanagement.AddGroupMemberRequest{
			Group: &groupsmanagement.GroupMember{
				Id:       id,
				Memberid: respbeneficiary.Account.Id,
				Groupid:  resp.Group.ToStorageType().ID,
				Data:     data.GetStructValue(),
			},
		}

		_, err = h.services.GRPC.GroupsManagement.AddGroupMember(context.TODO(), request_organizatio)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", resp.Group.ToStorageType().ID), http.StatusFound)
		return
	}
	//////////find all groups to store the adresse passenger///////
	// grp := &groupsmanagement.GetGroupsBatchMemberRequest{

	// 	Groupids: []string{resp.Group.ToStorageType().ID},
	// }
	// s, err := h.services.GRPC.GroupsManagement.GetGroupsBatchMember(context.TODO(), grp)
	// if err != nil {
	// 	fmt.Println(err)
	// 	w.WriteHeader(http.StatusInternalServerError)
	// 	return
	// }
	// groups := map[string]any{}

	// if err == nil {
	// 	for _, g := range s.Groups {
	// 		groups[g.Memberid] = g.ToStorageType()
	// 	}
	// }
	//////////find all groups to store the adresse passenger///////
	///////////try to optimise the code ////////////////////////////
	groups, _ := h.services.GetGroupsMemberMap(resp.Group.ToStorageType().ID)
	//fmt.Println(groups)
	var number string = strconv.Itoa(len(resp.Group.Members))
	/////////////////////
	accountsBeneficaire, err := h.beneficiaries(r)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Renderer.DisplayGroupCovoiturage(w, r, number, resp.Group.ToStorageType().ID, Depart, Arrive, accounts, cacheid, searched, beneficiary, resp.Group.ToStorageType(), accountsBeneficaire, groups)
}

func (h *ApplicationHandler) UpdateGroupCovoiturage(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]
	groupid := vars["groupid"]
	memberid := vars["memberid"]

	if r.Method == "POST" {

		//////////get groupid covoiturage//////////
		request := &groupsmanagement.GetGroupRequest{
			Id: groupid,
		}

		resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		//////////////////////////get group member////////////////////////////////

		reequest := &groupsmanagement.GetGroupMemberRequest{
			Id: id,
		}

		ressp, err := h.services.GRPC.GroupsManagement.GetGroupMember(context.TODO(), reequest)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		req := &groupsmanagement.UnsubscribeMemberRequest{
			Id: ressp.Group.Id,
		}

		_, errr := h.services.GRPC.GroupsManagement.UnsubscribeMember(context.TODO(), req)
		if errr != nil {
			fmt.Println(errr)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		members := resp.Group.Members
		for i := 0; i < len(members); i++ {
			if members[i] == memberid {
				members = append(members[:i], members[(i+1):]...)
				resp.Group.Members = members
				reequest := &groupsmanagement.UnsubscribeRequest{
					Groupid:  resp.Group.ToStorageType().ID,
					Memberid: memberid,
				}

				_, err := h.services.GRPC.GroupsManagement.Unsubscribe(context.TODO(), reequest)
				if err != nil {
					fmt.Println(err)
					w.WriteHeader(http.StatusInternalServerError)
					return
				}
			}

		}
		http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", groupid), http.StatusFound)
		/*
		 I must add "return" to resolve the err
		 http: superfluous response.WriteHeader call from git.coopgo.io/coopgo-apps/parcoursmob/renderer.(*Renderer).Render (renderer.go:50)
		*/
		return
	}
	h.Renderer.UpdateGroupCovoiturage(w, r, groupid, memberid)
}
