package api

import (
	"encoding/json"
	"errors"
	"github.com/google/uuid"
	"go-nkode/config"
	"go-nkode/internal/entities"
	"go-nkode/internal/models"
	"go-nkode/internal/security"
	"log"
	"net/http"
	"strings"
)

type NKodeHandler struct {
	Api NKodeAPI
}

const (
	CreateNewCustomer            = "/create-new-customer"
	GenerateSignupResetInterface = "/generate-signup-reset-interface"
	SetNKode                     = "/set-nkode"
	ConfirmNKode                 = "/confirm-nkode"
	GetLoginInterface            = "/get-login-interface"
	Login                        = "/login"
	RenewAttributes              = "/renew-attributes"
	RandomSvgInterface           = "/random-svg-interface"
	RefreshToken                 = "/refresh-token"
	ResetNKode                   = "/reset-nkode"
)

const (
	malformedCustomerId     = "malformed customer id"
	malformedUserEmail      = "malformed user email"
	malformedSessionId      = "malformed session id"
	invalidKeypadDimensions = "invalid keypad dimensions"
)

func (h *NKodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case CreateNewCustomer:
		h.CreateNewCustomerHandler(w, r)
	case GenerateSignupResetInterface:
		h.GenerateSignupResetInterfaceHandler(w, r)
	case SetNKode:
		h.SetNKodeHandler(w, r)
	case ConfirmNKode:
		h.ConfirmNKodeHandler(w, r)
	case GetLoginInterface:
		h.GetLoginInterfaceHandler(w, r)
	case Login:
		h.LoginHandler(w, r)
	case RenewAttributes:
		h.RenewAttributesHandler(w, r)
	case RandomSvgInterface:
		h.RandomSvgInterfaceHandler(w, r)
	case RefreshToken:
		h.RefreshTokenHandler(w, r)
	case ResetNKode:
		h.ResetNKode(w, r)

	default:
		w.WriteHeader(http.StatusNotFound)
		_, err := w.Write([]byte("404 not found"))
		log.Println(err)
	}
}

// CreateNewCustomerHandler handles the creation of a new customer.
// @Summary Create a new customer
// @Description Creates a new customer based on the provided policy information.
// @Tags customers
// @Accept json
// @Produce json
// @Param NewCustomerPost body NewCustomerPost true "Customer creation data"
// @Success 200 {object} CreateNewCustomerResp
// @Router /create-new-customer [post]
func (h *NKodeHandler) CreateNewCustomerHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("create new customer")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var customerPost models.NewCustomerPost
	if err := decodeJson(w, r, &customerPost); err != nil {
		return
	}
	customerId, err := h.Api.CreateNewCustomer(customerPost.NKodePolicy, nil)
	if err != nil {
		handleError(w, err)
		return
	}
	respBody := models.CreateNewCustomerResp{
		CustomerId: uuid.UUID(*customerId).String(),
	}
	marshalAndWriteBytes(w, respBody)
}

func (h *NKodeHandler) GenerateSignupResetInterfaceHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("signup/reset interface")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var signupResetPost models.GenerateSignupRestInterfacePost
	if err := decodeJson(w, r, &signupResetPost); err != nil {
		return
	}
	kp := entities.KeypadDimension{
		AttrsPerKey: signupResetPost.AttrsPerKey,
		NumbOfKeys:  signupResetPost.NumbOfKeys,
	}
	if err := kp.IsValidKeypadDimension(); err != nil {
		badRequest(w, invalidKeypadDimensions)
		return
	}
	customerId, err := uuid.Parse(signupResetPost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	userEmail, err := models.ParseEmail(signupResetPost.UserEmail)
	if err != nil {
		badRequest(w, malformedUserEmail)
		return
	}
	resp, err := h.Api.GenerateSignupResetInterface(userEmail, models.CustomerID(customerId), kp, signupResetPost.Reset)
	if err != nil {
		handleError(w, err)
		return
	}
	marshalAndWriteBytes(w, resp)
}

func (h *NKodeHandler) SetNKodeHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("set nkode")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var setNKodePost models.SetNKodePost
	if err := decodeJson(w, r, &setNKodePost); err != nil {
		return
	}
	customerId, err := uuid.Parse(setNKodePost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	sessionId, err := uuid.Parse(setNKodePost.SessionId)
	if err != nil {
		badRequest(w, malformedSessionId)
		return
	}
	confirmInterface, err := h.Api.SetNKode(models.CustomerID(customerId), models.SessionId(sessionId), setNKodePost.KeySelection)
	if err != nil {
		handleError(w, err)
		return
	}
	respBody := models.SetNKodeResp{UserInterface: confirmInterface}
	marshalAndWriteBytes(w, respBody)
}

func (h *NKodeHandler) ConfirmNKodeHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("confirm nkode")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}

	var confirmNKodePost models.ConfirmNKodePost
	if err := decodeJson(w, r, &confirmNKodePost); err != nil {
		return
	}
	customerId, err := uuid.Parse(confirmNKodePost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	sessionId, err := uuid.Parse(confirmNKodePost.SessionId)
	if err != nil {
		badRequest(w, malformedSessionId)
		return
	}
	if err = h.Api.ConfirmNKode(models.CustomerID(customerId), models.SessionId(sessionId), confirmNKodePost.KeySelection); err != nil {
		handleError(w, err)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func (h *NKodeHandler) GetLoginInterfaceHandler(w http.ResponseWriter, r *http.Request) {
	log.Print("get login interface")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var loginInterfacePost models.GetLoginInterfacePost
	if err := decodeJson(w, r, &loginInterfacePost); err != nil {
		return
	}
	customerId, err := uuid.Parse(loginInterfacePost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	userEmail, err := models.ParseEmail(loginInterfacePost.UserEmail)
	if err != nil {
		badRequest(w, malformedUserEmail)
	}
	loginInterface, err := h.Api.GetLoginInterface(userEmail, models.CustomerID(customerId))
	if err != nil {
		handleError(w, err)
		return
	}

	marshalAndWriteBytes(w, loginInterface)
}

func (h *NKodeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("login")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var loginPost models.LoginPost
	if err := decodeJson(w, r, &loginPost); err != nil {
		return
	}
	customerId, err := uuid.Parse(loginPost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	userEmail, err := models.ParseEmail(loginPost.UserEmail)
	if err != nil {
		badRequest(w, malformedUserEmail)
		return
	}
	jwtTokens, err := h.Api.Login(models.CustomerID(customerId), userEmail, loginPost.KeySelection)
	if err != nil {
		handleError(w, err)
		return
	}

	marshalAndWriteBytes(w, jwtTokens)
}

func (h *NKodeHandler) RenewAttributesHandler(w http.ResponseWriter, r *http.Request) {
	println("renew attributes")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
		return
	}
	var renewAttributesPost models.RenewAttributesPost
	if err := decodeJson(w, r, &renewAttributesPost); err != nil {
		return
	}
	customerId, err := uuid.Parse(renewAttributesPost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	if err = h.Api.RenewAttributes(models.CustomerID(customerId)); err != nil {
		handleError(w, err)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func (h *NKodeHandler) RandomSvgInterfaceHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("random svg interface")
	if r.Method != http.MethodGet {
		methodNotAllowed(w)
	}
	svgs, err := h.Api.RandomSvgInterface()
	if err != nil {
		handleError(w, err)
		return
	}

	respBody := models.RandomSvgInterfaceResp{
		Svgs:   svgs,
		Colors: models.SetColors,
	}

	marshalAndWriteBytes(w, respBody)
}

func (h *NKodeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Request) {
	println("refresh tokens")
	if r.Method != http.MethodGet {
		methodNotAllowed(w)
	}
	refreshToken, err := getBearerToken(r)
	if err != nil {
		forbidden(w)
		return
	}
	refreshClaims, err := security.ParseRegisteredClaimToken(refreshToken)
	customerId, err := uuid.Parse(refreshClaims.Issuer)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}
	userEmail, err := models.ParseEmail(refreshClaims.Subject)
	if err != nil {
		badRequest(w, malformedUserEmail)
		log.Println(err)
		return
	}
	accessToken, err := h.Api.RefreshToken(userEmail, models.CustomerID(customerId), refreshToken)

	if err != nil {
		handleError(w, err)
		log.Println(err)
		return
	}
	marshalAndWriteBytes(w, models.RefreshTokenResp{AccessToken: accessToken})
}

func (h *NKodeHandler) ResetNKode(w http.ResponseWriter, r *http.Request) {
	println("reset nkode")
	if r.Method != http.MethodPost {
		methodNotAllowed(w)
	}
	var resetNKodePost models.ResetNKodePost
	if err := decodeJson(w, r, &resetNKodePost); err != nil {
		return
	}

	customerId, err := uuid.Parse(resetNKodePost.CustomerId)
	if err != nil {
		badRequest(w, malformedCustomerId)
		return
	}

	userEmail, err := models.ParseEmail(resetNKodePost.UserEmail)
	if err != nil {
		badRequest(w, malformedUserEmail)
		return
	}

	if err = h.Api.ResetNKode(userEmail, models.CustomerID(customerId)); err != nil {
		internalServerError(w)
		log.Println(err)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func decodeJson(w http.ResponseWriter, r *http.Request, post any) error {
	if r.Body == nil {
		badRequest(w, "unable to parse body")
		log.Println("error decoding json: body is nil")
		return errors.New("body is nil")
	}
	err := json.NewDecoder(r.Body).Decode(&post)
	if err != nil {
		badRequest(w, "unable to parse body")
		log.Println("error decoding json: ", err)
		return err
	}
	return nil
}

func internalServerError(w http.ResponseWriter) {
	log.Print("500 internal server error")
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("500 Internal Server Error"))
}

func badRequest(w http.ResponseWriter, msg string) {
	log.Print("bad request: ", msg)
	w.WriteHeader(http.StatusBadRequest)
	if msg == "" {
		w.Write([]byte("400 Bad Request"))
	} else {
		w.Write([]byte(msg))
	}
}

func methodNotAllowed(w http.ResponseWriter) {
	log.Print("405 method not allowed")
	w.WriteHeader(http.StatusMethodNotAllowed)
	w.Write([]byte("405 method not allowed"))
}

func forbidden(w http.ResponseWriter) {
	log.Print("403 forbidden")
	w.WriteHeader(http.StatusForbidden)
	w.Write([]byte("403 Forbidden"))
}

func handleError(w http.ResponseWriter, err error) {
	log.Print("handling error: ", err)
	statusCode, exists := config.HttpErrMap[err]
	if !exists {
		internalServerError(w)
		return
	}
	switch statusCode {
	case http.StatusBadRequest:
		badRequest(w, err.Error())
	case http.StatusForbidden:
		forbidden(w)
	case http.StatusInternalServerError:
		internalServerError(w)
	default:
		log.Print("unknown error: ", err)
		internalServerError(w)
	}
}

func getBearerToken(r *http.Request) (string, error) {
	authHeader := r.Header.Get("Authorization")
	// Check if the Authorization header is present and starts with "Bearer "
	if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
		return "", errors.New("forbidden")
	}
	token := strings.TrimPrefix(authHeader, "Bearer ")
	return token, nil
}

func marshalAndWriteBytes(w http.ResponseWriter, data any) {
	respBytes, err := json.Marshal(data)

	if err != nil {
		internalServerError(w)
		log.Println(err)
		return
	}
	_, err = w.Write(respBytes)

	if err != nil {
		internalServerError(w)
		log.Println(err)
		return
	}
}
