package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"git.tornberg.me/go-cart-actor/pkg/actor"
	"git.tornberg.me/go-cart-actor/pkg/cart"
	"git.tornberg.me/mats/go-redis-inventory/pkg/inventory"
	amqp "github.com/rabbitmq/amqp091-go"
)

var tpl = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>s10r testing - checkout</title>
  </head>

  <body>
    %s
  </body>
</html>
`

func (a *App) getGrainFromOrder(ctx context.Context, order *CheckoutOrder) (*cart.CartGrain, error) {
	cartId, ok := cart.ParseCartId(order.MerchantReference1)
	if !ok {
		return nil, fmt.Errorf("invalid cart id in order reference: %s", order.MerchantReference1)
	}
	grain, err := a.pool.Get(ctx, uint64(cartId))
	if err != nil {
		return nil, fmt.Errorf("failed to get cart grain: %w", err)
	}
	return grain, nil
}

func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux, inventoryService inventory.InventoryService) {
	conn, err := amqp.Dial(amqpUrl)
	if err != nil {
		log.Fatalf("failed to connect to RabbitMQ: %v", err)
	}

	amqpListener := actor.NewAmqpListener(conn, func(id uint64, msg []actor.ApplyResult) (any, error) {
		return &CartChangeEvent{
			CartId:    cart.CartId(id),
			Mutations: msg,
		}, nil
	})
	amqpListener.DefineTopics()
	a.pool.AddListener(amqpListener)
	orderHandler := NewAmqpOrderHandler(conn)
	orderHandler.DefineQueue()

	mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Klarna order confirmation push, method: %s", r.Method)
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		orderId := r.URL.Query().Get("order_id")
		log.Printf("Order confirmation push: %s", orderId)

		order, err := a.klarnaClient.GetOrder(r.Context(), orderId)

		if err != nil {
			log.Printf("Error creating request: %v\n", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		grain, err := a.getGrainFromOrder(r.Context(), order)
		if err != nil {
			logger.ErrorContext(r.Context(), "Unable to get grain from klarna order", "error", err.Error())
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		if inventoryService != nil {
			inventoryRequests := getInventoryRequests(grain.Items)
			err = inventoryService.ReserveInventory(r.Context(), inventoryRequests...)
			if err != nil {
				logger.WarnContext(r.Context(), "placeorder inventory reservation failed")
				w.WriteHeader(http.StatusNotAcceptable)
				return
			}
		}

		err = confirmOrder(r.Context(), order, orderHandler)
		if err != nil {
			log.Printf("Error confirming order: %v\n", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		err = triggerOrderCompleted(r.Context(), a.server, order)
		if err != nil {
			log.Printf("Error processing cart message: %v\n", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		err = a.klarnaClient.AcknowledgeOrder(r.Context(), orderId)
		if err != nil {
			log.Printf("Error acknowledging order: %v\n", err)
		}

		w.WriteHeader(http.StatusOK)
	})

	mux.HandleFunc("GET /checkout", a.server.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")
		w.WriteHeader(http.StatusOK)
		_, err := fmt.Fprintf(w, tpl, order.HTMLSnippet)
		return err
	}))

	mux.HandleFunc("GET /confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {

		orderId := r.PathValue("order_id")
		order, err := a.klarnaClient.GetOrder(r.Context(), orderId)

		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(err.Error()))
			return
		}

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		if order.Status == "checkout_complete" {
			http.SetCookie(w, &http.Cookie{
				Name:     "cartid",
				Value:    "",
				Path:     "/",
				Secure:   true,
				HttpOnly: true,
				Expires:  time.Unix(0, 0),
				SameSite: http.SameSiteLaxMode,
			})
		}

		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, tpl, order.HTMLSnippet)
	})
	mux.HandleFunc("/notification", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Klarna order notification, method: %s", r.Method)
		logger.InfoContext(r.Context(), "Klarna order notification received", "method", r.Method)
		if r.Method != "POST" {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		order := &CheckoutOrder{}
		err := json.NewDecoder(r.Body).Decode(order)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
		}
		log.Printf("Klarna order notification: %s", order.ID)
		logger.InfoContext(r.Context(), "Klarna order notification received", "order_id", order.ID)

		w.WriteHeader(http.StatusOK)
	})
	mux.HandleFunc("POST /validate", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Klarna order validation, method: %s", r.Method)
		if r.Method != "POST" {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		order := &CheckoutOrder{}
		err := json.NewDecoder(r.Body).Decode(order)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
		}
		logger.InfoContext(r.Context(), "Klarna order validation received", "order_id", order.ID, "cart_id", order.MerchantReference1)
		grain, err := a.getGrainFromOrder(r.Context(), order)
		if err != nil {
			logger.ErrorContext(r.Context(), "Unable to get grain from klarna order", "error", err.Error())
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		if inventoryService != nil {
			inventoryRequests := getInventoryRequests(grain.Items)
			_, err = inventoryService.ReservationCheck(r.Context(), inventoryRequests...)
			if err != nil {
				logger.WarnContext(r.Context(), "placeorder inventory check failed")
				w.WriteHeader(http.StatusNotAcceptable)
				return
			}
		}

		w.WriteHeader(http.StatusOK)
	})
}
