package application

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"image/png"
	"io"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
	"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
	profilepictures "git.coopgo.io/coopgo-apps/parcoursmob/utils/profile-pictures"
	"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
	filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
	agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
	agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
	fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
	fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
	groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
	"git.coopgo.io/coopgo-platform/groups-management/storage"
	mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
	mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
	solidarity_service "git.coopgo.io/sbouaram/solidarity-service/servers/grpc/proto"


	"github.com/google/uuid"
	"github.com/gorilla/mux"
	"google.golang.org/protobuf/types/known/structpb"
)

type BeneficiariesForm struct {
	FirstName   string     `json:"first_name" validate:"required"`
	LastName    string     `json:"last_name" validate:"required"`
	Email       string     `json:"email" validate:"required,email"`
	Birthdate   *time.Time `json:"birthdate" validate:"required"`
	PhoneNumber string     `json:"phone_number" validate:"required,phoneNumber"`
	FileNumber  string     `json:"file_number"`
	Address     any        `json:"address,omitempty"`
	Gender      string     `json:"gender"`
}

type Event_Beneficiary interface {
	Name() string
	Date() time.Time
	DateEnd() time.Time
	Type() string
	Db() string
	ID() string
	Icons() string
	Status() int
}

type Event struct {
	IDVal      string
	NameVal    string
	DateVal    time.Time
	DateEndVal time.Time
	TypeVal    string
	DbVal      string
	Deleted    bool
	IconSet    string
	StatusVal  int
}

func (e Event) Name() string {
	return e.NameVal
}

func (e Event) Date() time.Time {
	return e.DateVal
}

func (e Event) DateEnd() time.Time {
	return e.DateEndVal
}

func (e Event) Type() string {
	return e.TypeVal
}

func (e Event) ID() string {
	return e.IDVal
}

func (e Event) Db() string {
	return e.DbVal
}

func (e Event) Icons() string {
	return e.IconSet
}

func (e Event) Status() int {
	return e.StatusVal
}

func sortByDate(events []Event_Beneficiary) {
	sort.Slice(events, func(i, j int) bool {
		return events[i].Date().After(events[j].Date())
	})
}

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

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

	sort.Sort(sorting.BeneficiariesByName(accounts))

	cacheid := uuid.NewString()
	h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
	h.Renderer.BeneficiariesList(w, r, accounts, cacheid)
}

func (h *ApplicationHandler) BeneficiaryCreate(w http.ResponseWriter, r *http.Request) {
	g := r.Context().Value(identification.GroupKey)
	if g == nil {
		fmt.Println("Create beneficiary : could not find group")
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	group := g.(storage.Group)

	if r.Method == "POST" {

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

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

		request := &mobilityaccounts.RegisterRequest{
			Account: &mobilityaccounts.Account{
				Namespace: "parcoursmob_beneficiaries",
				Data:      data.GetStructValue(),
			},
		}

		resp, err := h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)

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

		subscribe := &groupsmanagement.SubscribeRequest{
			Groupid:  group.ID,
			Memberid: resp.Account.Id,
		}

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

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

		http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)

		return
	}
	h.Renderer.BeneficiaryCreate(w, r)
}

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

	documents := h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiaryID)

	request := &mobilityaccounts.GetAccountRequest{
		Id: beneficiaryID,
	}

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

	subscriptionrequest := &agenda.GetSubscriptionByUserRequest{
		Subscriber: beneficiaryID,
	}

	subcriptionresp, err := h.services.GRPC.Agenda.GetSubscriptionByUser(context.TODO(), subscriptionrequest)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	events := []agendastorage.Event{}
	currentTime := time.Now().Truncate(24 * time.Hour)

	for _, e := range subcriptionresp.Subscription {
		eventresquest := &agenda.GetEventRequest{
			Id: e.Eventid,
		}
		eventresp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), eventresquest)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		events = append(events, eventresp.Event.ToStorageType())
	}

	sort.Sort(sorting.EventsByStartdate(events))

	bookingsrequest := &fleets.GetDriverBookingsRequest{
		Driver: beneficiaryID,
	}
	bookingsresp, err := h.services.GRPC.Fleets.GetDriverBookings(context.TODO(), bookingsrequest)
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	bookings := []fleetsstorage.Booking{}

	for _, b := range bookingsresp.Bookings {
		bookings = append(bookings, b.ToStorageType())
	}

	var events_list []Event_Beneficiary
	var status_event int

	for _, e := range events {

		if e.Startdate.After(currentTime) {
			status_event = 1
		} else if e.Startdate.Before(currentTime) && e.Enddate.After(currentTime) || e.Enddate.Equal(currentTime) {
			status_event = 2
		} else {
			status_event = 3
		}

		event := Event{
			NameVal:    e.Name,
			DateVal:    e.Startdate,
			DateEndVal: e.Enddate,
			TypeVal:    e.Type,
			IDVal:      e.ID,
			DbVal:      "/app/agenda/",
			IconSet:    "calendar",
			StatusVal:  status_event,
		}

		events_list = append(events_list, event)
	}

	var status_booking int
	for _, b := range bookings {

		if b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
			GetVehiculeRequest := &fleets.GetVehicleRequest{
				Vehicleid: b.Vehicleid,
			}

			GetVehiculeResp, err := h.services.GRPC.Fleets.GetVehicle(context.Background(), GetVehiculeRequest)
			if err != nil {
				fmt.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			if b.Startdate.After(currentTime) {
				status_booking = 1
			} else if b.Startdate.Before(currentTime) && b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
				status_booking = 2
			} else {
				status_booking = 3
			}

			event := Event{
				NameVal:    GetVehiculeResp.Vehicle.ToStorageType().Data["name"].(string),
				DateVal:    b.Startdate,
				DateEndVal: b.Enddate,
				TypeVal:    "Réservation de véhicule",
				IDVal:      b.ID,
				DbVal:      "/app/vehicles-management/bookings/",
				IconSet:    "vehicle",
				StatusVal:  status_booking,
			}

			events_list = append(events_list, event)
		}
	}
	sortByDate(events_list)

	groupsrequest := &groupsmanagement.GetGroupsRequest{
		Namespaces: []string{"parcoursmob_organizations"},
		Member:     beneficiaryID,
	}

	groupsresp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), groupsrequest)
	if err != nil {
		fmt.Println(err)
	}

	organizations := []any{}
	for _, o := range groupsresp.Groups {
		organizations = append(organizations, o.ToStorageType())
	}
	
	beneficiaries_file_types := h.config.GetStringSlice("modules.beneficiaries.documents_types")
	file_types_map := h.config.GetStringMapString("storage.files.file_types")

	if r.Method == "POST" {

		request := &mobilityaccounts.GetAccountRequest{
			Id: beneficiaryID,
		}
	
		resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		fields := resp.Account.Data.Fields

		firstName, ok := fields["first_name"]
		if !ok {
			fmt.Println("first_name not found in the data")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		lastName, ok := fields["last_name"]
		if !ok {
			fmt.Println("last_name not found in the data")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		passenger := solidarity_service.PassengerTripRequest {
			Passenger : &solidarity_service.User {
				Id: resp.Account.Id,
				FirstName: firstName.Kind.(*structpb.Value_StringValue).StringValue,
				LastName: lastName.Kind.(*structpb.Value_StringValue).StringValue,
				Alias: " ",
				Grade: 0,
				Picture: " ",
				Gender: " ",
				VerifiedIdentity: true,

			},
		}

		silvermobiPassenger, err := h.services.GRPC.SolidarityService.SetPassengerTrip(context.TODO(), &passenger)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		t := silvermobiPassenger
		t = t		
	}

	solidarity_booking := &solidarity_service.GetBookingsByStatusRequest{
		Status : 0,   /// 0 : pending
		UserId : beneficiaryID,
		Type: 1,      /// 1 : passenger
	}

	solidarity_bookings_object, err := h.services.GRPC.SolidarityService.GetBookingsByStatus(context.TODO(), solidarity_booking)


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

	var solidarity_bookings_list []any

	for _, o := range solidarity_bookings_object.Booking {
		if(o.PassengerPickupDate != nil){
			solidarity_bookings_list = append(solidarity_bookings_list, o)
		}
	}
	
	h.Renderer.BeneficiaryDisplay(w, r, resp.Account.ToStorageType(), bookings, organizations, beneficiaries_file_types, file_types_map, documents, events_list, solidarity_bookings_list)
}

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

	if r.Method == "POST" {

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

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

		request := &mobilityaccounts.UpdateDataRequest{
			Account: &mobilityaccounts.Account{
				Id:        beneficiaryID,
				Namespace: "parcoursmob_beneficiaries",
				Data:      data.GetStructValue(),
			},
		}

		resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)

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

		http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)

		return
	}

	request := &mobilityaccounts.GetAccountRequest{
		Id: beneficiaryID,
	}

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

	//TODO filter namespaces
	//TODO filter groups

	h.Renderer.BeneficiaryUpdate(w, r, resp.Account.ToStorageType())
}

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

	request := &mobilityaccounts.GetAccountRequest{
		Id: beneficiaryID,
	}

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

	account := resp.Account.ToStorageType()

	firstName := account.Data["first_name"].(string)
	lastName := account.Data["last_name"].(string)
	picture := profilepictures.DefaultProfilePicture(strings.ToUpper(firstName[0:1] + lastName[0:1]))

	buffer := new(bytes.Buffer)
	if err := png.Encode(buffer, picture); err != nil {
		log.Println("unable to encode image.")
	}

	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
	if _, err := w.Write(buffer.Bytes()); err != nil {
		log.Println("unable to write image.")
	}
}

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

	//r.ParseForm()
	r.ParseMultipartForm(100 * 1024 * 1024)

	document_type := r.FormValue("type")
	document_name := r.FormValue("name")

	file, header, err := r.FormFile("file-upload")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()

	fileid := uuid.NewString()

	metadata := map[string]string{
		"type": document_type,
		"name": document_name,
	}

	if err := h.filestorage.Put(file, filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s_%s", beneficiaryID, fileid, header.Filename), header.Size, metadata); err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)

}

func (h *ApplicationHandler) BeneficiaryDocumentDownload(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	beneficiaryID := vars["beneficiaryid"]
	document := vars["document"]

	file, info, err := h.filestorage.Get(filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s", beneficiaryID, document))
	if err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", info.ContentType)
	if _, err = io.Copy(w, file); err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)

}

func filterAccount(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 BeneficiariesEventList()

func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
	var accounts = []mobilityaccountsstorage.Account{}
	g := r.Context().Value(identification.GroupKey)
	if g == nil {
		return accounts, errors.New("no group provided")
	}

	group := g.(storage.Group)

	request := &mobilityaccounts.GetAccountsBatchRequest{
		Accountids: group.Members,
	}

	resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request)
	if err != nil {
		return accounts, err
	}

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

	return accounts, err
}

func parseBeneficiariesForm(r *http.Request) (map[string]any, error) {
	if err := r.ParseForm(); err != nil {
		return nil, err
	}

	var date *time.Time

	if r.PostFormValue("birthdate") != "" {
		d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate"))
		if err != nil {
			return nil, err
		}
		date = &d 
	}

	formData := BeneficiariesForm{
		FirstName:   r.PostFormValue("first_name"),
		LastName:    r.PostFormValue("last_name"),
		Email:       r.PostFormValue("email"),
		Birthdate:   date,
		PhoneNumber: r.PostFormValue("phone_number"),
		FileNumber:  r.PostFormValue("file_number"), 
		Gender:      r.PostFormValue("gender"),
	}

	if r.PostFormValue("address") != "" {
		var a any
		json.Unmarshal([]byte(r.PostFormValue("address")), &a)

		formData.Address = a
	}

	validate := formvalidators.New()
	if err := validate.Struct(formData); err != nil {
		return nil, err
	}

	d, err := json.Marshal(formData)
	if err != nil {
		return nil, err
	}

	var dataMap map[string]any
	err = json.Unmarshal(d, &dataMap)
	if err != nil {
		return nil, err
	}

	return dataMap, nil
}
