package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"git.tornberg.me/go-cart-actor/pkg/actor"
	"git.tornberg.me/go-cart-actor/pkg/cart"
	messages "git.tornberg.me/go-cart-actor/pkg/messages"
	"git.tornberg.me/go-cart-actor/pkg/voucher"
	"github.com/gogo/protobuf/proto"
)

type PoolServer struct {
	actor.GrainPool[*cart.CartGrain]
	pod_name     string
	klarnaClient *KlarnaClient
}

func NewPoolServer(pool actor.GrainPool[*cart.CartGrain], pod_name string, klarnaClient *KlarnaClient) *PoolServer {
	return &PoolServer{
		GrainPool:    pool,
		pod_name:     pod_name,
		klarnaClient: klarnaClient,
	}
}

func (s *PoolServer) ApplyLocal(id cart.CartId, mutation ...proto.Message) (*actor.MutationResult[*cart.CartGrain], error) {
	return s.Apply(uint64(id), mutation...)
}

func (s *PoolServer) GetCartHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	grain, err := s.Get(uint64(id))
	if err != nil {
		return err
	}

	return s.WriteResult(w, grain)
}

func (s *PoolServer) AddSkuToCartHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	sku := r.PathValue("sku")
	msg, err := GetItemAddMessage(sku, 1, getCountryFromHost(r.Host), nil)
	if err != nil {
		return err
	}
	data, err := s.ApplyLocal(id, msg)
	if err != nil {
		return err
	}
	return s.WriteResult(w, data)
}

func (s *PoolServer) WriteResult(w http.ResponseWriter, result any) error {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("X-Pod-Name", s.pod_name)
	if result == nil {

		w.WriteHeader(http.StatusInternalServerError)

		return nil
	}
	w.WriteHeader(http.StatusOK)
	enc := json.NewEncoder(w)
	err := enc.Encode(result)
	return err
}

func (s *PoolServer) DeleteItemHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {

	itemIdString := r.PathValue("itemId")
	itemId, err := strconv.ParseInt(itemIdString, 10, 64)
	if err != nil {
		return err
	}
	data, err := s.ApplyLocal(id, &messages.RemoveItem{Id: uint32(itemId)})
	if err != nil {
		return err
	}
	return s.WriteResult(w, data)
}

type SetDeliveryRequest struct {
	Provider    string                `json:"provider"`
	Items       []uint32              `json:"items"`
	PickupPoint *messages.PickupPoint `json:"pickupPoint,omitempty"`
}

func (s *PoolServer) SetDeliveryHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {

	delivery := SetDeliveryRequest{}
	err := json.NewDecoder(r.Body).Decode(&delivery)
	if err != nil {
		return err
	}
	data, err := s.ApplyLocal(id, &messages.SetDelivery{
		Provider:    delivery.Provider,
		Items:       delivery.Items,
		PickupPoint: delivery.PickupPoint,
	})
	if err != nil {
		return err
	}
	return s.WriteResult(w, data)
}

func (s *PoolServer) SetPickupPointHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {

	deliveryIdString := r.PathValue("deliveryId")
	deliveryId, err := strconv.ParseInt(deliveryIdString, 10, 64)
	if err != nil {
		return err
	}
	pickupPoint := messages.PickupPoint{}
	err = json.NewDecoder(r.Body).Decode(&pickupPoint)
	if err != nil {
		return err
	}
	reply, err := s.ApplyLocal(id, &messages.SetPickupPoint{
		DeliveryId: uint32(deliveryId),
		Id:         pickupPoint.Id,
		Name:       pickupPoint.Name,
		Address:    pickupPoint.Address,
		City:       pickupPoint.City,
		Zip:        pickupPoint.Zip,
		Country:    pickupPoint.Country,
	})
	if err != nil {
		return err
	}
	return s.WriteResult(w, reply)
}

func (s *PoolServer) RemoveDeliveryHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {

	deliveryIdString := r.PathValue("deliveryId")
	deliveryId, err := strconv.Atoi(deliveryIdString)
	if err != nil {
		return err
	}
	reply, err := s.ApplyLocal(id, &messages.RemoveDelivery{Id: uint32(deliveryId)})
	if err != nil {
		return err
	}
	return s.WriteResult(w, reply)
}

func (s *PoolServer) QuantityChangeHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	changeQuantity := messages.ChangeQuantity{}
	err := json.NewDecoder(r.Body).Decode(&changeQuantity)
	if err != nil {
		return err
	}
	reply, err := s.ApplyLocal(id, &changeQuantity)
	if err != nil {
		return err
	}
	return s.WriteResult(w, reply)
}

type Item struct {
	Sku      string  `json:"sku"`
	Quantity int     `json:"quantity"`
	StoreId  *string `json:"storeId,omitempty"`
}

type SetCartItems struct {
	Country string `json:"country"`
	Items   []Item `json:"items"`
}

func getMultipleAddMessages(items []Item, country string) []proto.Message {
	wg := sync.WaitGroup{}
	mu := sync.Mutex{}
	msgs := make([]proto.Message, 0, len(items))
	for _, itm := range items {
		wg.Go(
			func() {
				msg, err := GetItemAddMessage(itm.Sku, itm.Quantity, country, itm.StoreId)
				if err != nil {
					log.Printf("error adding item %s: %v", itm.Sku, err)
					return
				}
				mu.Lock()
				msgs = append(msgs, msg)
				mu.Unlock()
			})
	}
	wg.Wait()
	return msgs
}

func (s *PoolServer) SetCartItemsHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	setCartItems := SetCartItems{}
	err := json.NewDecoder(r.Body).Decode(&setCartItems)
	if err != nil {
		return err
	}

	msgs := make([]proto.Message, 0, len(setCartItems.Items)+1)
	msgs = append(msgs, &messages.ClearCartRequest{})
	msgs = append(msgs, getMultipleAddMessages(setCartItems.Items, setCartItems.Country)...)

	reply, err := s.ApplyLocal(id, msgs...)
	if err != nil {
		return err
	}
	return s.WriteResult(w, reply)
}

func (s *PoolServer) AddMultipleItemHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	setCartItems := SetCartItems{}
	err := json.NewDecoder(r.Body).Decode(&setCartItems)
	if err != nil {
		return err
	}

	reply, err := s.ApplyLocal(id, getMultipleAddMessages(setCartItems.Items, setCartItems.Country)...)
	if err != nil {
		return err
	}
	return s.WriteResult(w, reply)
}

type AddRequest struct {
	Sku      string  `json:"sku"`
	Quantity int32   `json:"quantity"`
	Country  string  `json:"country"`
	StoreId  *string `json:"storeId"`
}

func (s *PoolServer) AddSkuRequestHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
	addRequest := AddRequest{Quantity: 1}
	err := json.NewDecoder(r.Body).Decode(&addRequest)
	if err != nil {
		return err
	}
	msg, err := GetItemAddMessage(addRequest.Sku, int(addRequest.Quantity), addRequest.Country, addRequest.StoreId)
	if err != nil {
		return err
	}
	reply, err := s.ApplyLocal(id, msg)
	if err != nil {
		return err
	}

	return s.WriteResult(w, reply)
}

// func (s *PoolServer) HandleConfirmation(w http.ResponseWriter, r *http.Request, id CartId) error {
// 	orderId := r.PathValue("orderId")
// 	if orderId == "" {
// 		return fmt.Errorf("orderId is empty")
// 	}
// 	order, err := KlarnaInstance.GetOrder(orderId)

// 	if err != nil {
// 		return err
// 	}

// 	w.Header().Set("Content-Type", "application/json")
// 	w.Header().Set("X-Pod-Name", s.pod_name)
// 	w.Header().Set("Cache-Control", "no-cache")
// 	w.Header().Set("Access-Control-Allow-Origin", "*")
// 	w.WriteHeader(http.StatusOK)
// 	return json.NewEncoder(w).Encode(order)
// }

func getCurrency(country string) string {
	if country == "no" {
		return "NOK"
	}
	return "SEK"
}

func getLocale(country string) string {
	if country == "no" {
		return "nb-no"
	}
	return "sv-se"
}

func (s *PoolServer) CreateOrUpdateCheckout(host string, id cart.CartId) (*CheckoutOrder, error) {
	country := getCountryFromHost(host)
	meta := &CheckoutMeta{
		Terms:        fmt.Sprintf("https://%s/terms", host),
		Checkout:     fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", host),
		Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", host),
		Validation:   fmt.Sprintf("https://%s/validate", host),
		Push:         fmt.Sprintf("https://%s/push?order_id={checkout.order.id}", host),
		Country:      country,
		Currency:     getCurrency(country),
		Locale:       getLocale(country),
	}

	// Get current grain state (may be local or remote)
	grain, err := s.Get(uint64(id))
	if err != nil {
		return nil, err
	}

	// Build pure checkout payload
	payload, _, err := BuildCheckoutOrderPayload(grain, meta)
	if err != nil {
		return nil, err
	}

	if grain.OrderReference != "" {
		return s.klarnaClient.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
	} else {
		return s.klarnaClient.CreateOrder(bytes.NewReader(payload))
	}
}

func (s *PoolServer) ApplyCheckoutStarted(klarnaOrder *CheckoutOrder, id cart.CartId) (*actor.MutationResult[*cart.CartGrain], error) {
	// Persist initialization state via mutation (best-effort)
	return s.ApplyLocal(id, &messages.InitializeCheckout{
		OrderId:           klarnaOrder.ID,
		Status:            klarnaOrder.Status,
		PaymentInProgress: true,
	})
}

// func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
// 	klarnaOrder, err := s.CreateOrUpdateCheckout(r.Host, id)
// 	if err != nil {
// 		return err
// 	}

// 	s.ApplyCheckoutStarted(klarnaOrder, id)

// 	w.Header().Set("Content-Type", "application/json")
// 	return json.NewEncoder(w).Encode(klarnaOrder)
// }
//

func CookieCartIdHandler(fn func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		var id cart.CartId
		cookie, err := r.Cookie("cartid")
		if err != nil || cookie.Value == "" {
			id = cart.MustNewCartId()
			http.SetCookie(w, &http.Cookie{
				Name:     "cartid",
				Value:    id.String(),
				Secure:   r.TLS != nil,
				HttpOnly: true,
				Path:     "/",
				Expires:  time.Now().AddDate(0, 0, 14),
				SameSite: http.SameSiteLaxMode,
			})
			w.Header().Set("Set-Cart-Id", id.String())
		} else {
			parsed, ok := cart.ParseCartId(cookie.Value)
			if !ok {
				id = cart.MustNewCartId()
				http.SetCookie(w, &http.Cookie{
					Name:     "cartid",
					Value:    id.String(),
					Secure:   r.TLS != nil,
					HttpOnly: true,
					Path:     "/",
					Expires:  time.Now().AddDate(0, 0, 14),
					SameSite: http.SameSiteLaxMode,
				})
				w.Header().Set("Set-Cart-Id", id.String())
			} else {
				id = parsed
			}
		}

		err = fn(id, w, r)
		if err != nil {
			log.Printf("Server error, not remote error: %v\n", err)
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(err.Error()))
		}

	}
}

// Removed leftover legacy block after CookieCartIdHandler (obsolete code referencing cid/legacy)

func (s *PoolServer) RemoveCartCookie(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
	// Clear cart cookie (breaking change: do not issue a new legacy id here)
	http.SetCookie(w, &http.Cookie{
		Name:     "cartid",
		Value:    "",
		Path:     "/",
		Secure:   r.TLS != nil,
		HttpOnly: true,
		Expires:  time.Unix(0, 0),
		SameSite: http.SameSiteLaxMode,
	})
	w.WriteHeader(http.StatusOK)
	return nil
}

func CartIdHandler(fn func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		var id cart.CartId
		raw := r.PathValue("id")
		// If no id supplied, generate a new one
		if raw == "" {
			id := cart.MustNewCartId()
			w.Header().Set("Set-Cart-Id", id.String())
		} else {
			// Parse base62 cart id
			if parsedId, ok := cart.ParseCartId(raw); !ok {
				w.WriteHeader(http.StatusBadRequest)
				w.Write([]byte("cart id is invalid"))
				return
			} else {
				id = parsedId
			}
		}

		err := fn(id, w, r)
		if err != nil {
			log.Printf("Server error, not remote error: %v\n", err)
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(err.Error()))
		}
	}
}

func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error) func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error {
	return func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error {
		if ownerHost, ok := s.OwnerHost(uint64(cartId)); ok {
			handled, err := ownerHost.Proxy(uint64(cartId), w, r)
			if err == nil && handled {
				return nil
			}
		}

		return fn(w, r, cartId)

	}
}

type AddVoucherRequest struct {
	VoucherCode string `json:"code"`
}

func (s *PoolServer) AddVoucherHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
	data := &AddVoucherRequest{}
	json.NewDecoder(r.Body).Decode(data)
	v := voucher.Service{}
	msg, err := v.GetVoucher(data.VoucherCode)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	reply, err := s.ApplyLocal(cartId, msg)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	s.WriteResult(w, reply)
	return nil
}

func (s *PoolServer) SubscriptionDetailsHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
	data := &messages.UpsertSubscriptionDetails{}
	err := json.NewDecoder(r.Body).Decode(data)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	reply, err := s.ApplyLocal(cartId, data)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	s.WriteResult(w, reply)
	return nil
}

func (s *PoolServer) CheckoutHandler(fn func(order *CheckoutOrder, w http.ResponseWriter) error) func(w http.ResponseWriter, r *http.Request) {
	return CookieCartIdHandler(s.ProxyHandler(func(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
		orderId := r.URL.Query().Get("order_id")
		if orderId == "" {
			order, err := s.CreateOrUpdateCheckout(r.Host, cartId)
			if err != nil {
				return err
			}
			s.ApplyCheckoutStarted(order, cartId)
			return fn(order, w)
		}
		order, err := s.klarnaClient.GetOrder(orderId)
		if err != nil {
			return err
		}
		return fn(order, w)
	}))
}

func (s *PoolServer) RemoveVoucherHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {

	idStr := r.PathValue("voucherId")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	reply, err := s.ApplyLocal(cartId, &messages.RemoveVoucher{Id: uint32(id)})
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return err
	}
	s.WriteResult(w, reply)
	return nil
}

func (s *PoolServer) Serve() *http.ServeMux {

	mux := http.NewServeMux()
	mux.HandleFunc("OPTIONS /", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	})

	mux.HandleFunc("GET /", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler)))
	mux.HandleFunc("GET /add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
	mux.HandleFunc("POST /add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler)))
	mux.HandleFunc("POST /", CookieCartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
	mux.HandleFunc("POST /set", CookieCartIdHandler(s.ProxyHandler(s.SetCartItemsHandler)))
	mux.HandleFunc("DELETE /{itemId}", CookieCartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
	mux.HandleFunc("PUT /", CookieCartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
	mux.HandleFunc("DELETE /", CookieCartIdHandler(s.ProxyHandler(s.RemoveCartCookie)))
	mux.HandleFunc("POST /delivery", CookieCartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
	mux.HandleFunc("DELETE /delivery/{deliveryId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
	mux.HandleFunc("PUT /delivery/{deliveryId}/pickupPoint", CookieCartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
	mux.HandleFunc("PUT /voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
	mux.HandleFunc("PUT /subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler)))
	mux.HandleFunc("DELETE /voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))

	//mux.HandleFunc("GET /checkout", CookieCartIdHandler(s.ProxyHandler(s.HandleCheckout)))
	//mux.HandleFunc("GET /confirmation/{orderId}", CookieCartIdHandler(s.ProxyHandler(s.HandleConfirmation)))

	mux.HandleFunc("GET /byid/{id}", CartIdHandler(s.ProxyHandler(s.GetCartHandler)))
	mux.HandleFunc("GET /byid/{id}/add/{sku}", CartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
	mux.HandleFunc("POST /byid/{id}", CartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
	mux.HandleFunc("DELETE /byid/{id}/{itemId}", CartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
	mux.HandleFunc("PUT /byid/{id}", CartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
	mux.HandleFunc("POST /byid/{id}/delivery", CartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
	mux.HandleFunc("DELETE /byid/{id}/delivery/{deliveryId}", CartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
	mux.HandleFunc("PUT /byid/{id}/delivery/{deliveryId}/pickupPoint", CartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
	mux.HandleFunc("PUT /byid/{id}/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
	mux.HandleFunc("DELETE /byid/{id}/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
	//mux.HandleFunc("GET /byid/{id}/checkout", CartIdHandler(s.ProxyHandler(s.HandleCheckout)))
	//mux.HandleFunc("GET /byid/{id}/confirmation", CartIdHandler(s.ProxyHandler(s.HandleConfirmation)))

	return mux
}
