// Copyright (C) 2023 Marius Schellenberger

package types

import (
	"encoding/json"
	"io"
	"net/http"

	"github.com/go-logr/logr"
	"github.com/gorilla/mux"

	"git.giftfish.de/ston1th/keyctl/pkg/db"
)

type ContextData struct {
	Approver *Approver
	DB       *db.DB
}

func NewContext(w http.ResponseWriter, r *http.Request, data *ContextData, log logr.Logger) *Context {
	h := w.Header()
	h.Set("Content-Type", "application/json")
	return &Context{
		Code:     http.StatusOK,
		Request:  r,
		Response: w,
		Data:     data,
		Log:      log,
		ip:       clientip(r.RemoteAddr),
	}
}

// Context is the context object shared between http handlers
type Context struct {
	Code int

	Request  *http.Request
	Response http.ResponseWriter

	Data *ContextData
	Log  logr.Logger

	ip string
}

// Method returns the request method
func (c *Context) Method() string {
	return c.Request.Method
}

// Path returns the request URL path
func (c *Context) Path() string {
	return c.Request.URL.Path
}

// Header returns the given http header
func (c *Context) Header(name string) string {
	return c.Request.Header.Get(name)
}

// SetHeader sets the given http header
func (c *Context) SetHeader(name, value string) {
	h := c.Response.Header()
	h.Set(name, value)
}

// Form returns the given form value
func (c *Context) Form(name string) string {
	return c.Request.FormValue(name)
}

func (c *Context) ClientIP() string {
	return c.ip
}

// Var returns the given url variable
func (c *Context) Var(name string) string {
	return mux.Vars(c.Request)[name]
}

func (c *Context) log() {
	c.Log.Info("access",
		"addr", c.Request.RemoteAddr,
		"method", c.Request.Method,
		"path", c.Request.URL.Path,
		"code", c.Code,
	)
}

func (c *Context) NotFound() {
	c.Err(ErrNotFound)
}

// OK is a empty HTTP 200 JSON response
func (c *Context) OK() {
	c.log()
}

func (c *Context) ReadBody() (b []byte, err error) {
	b, err = io.ReadAll(c.Request.Body)
	c.Request.Body.Close()
	return
}

func (c *Context) Body(b []byte) {
	c.Response.Write(b)
	c.log()
}

// JSON is a json response
func (c *Context) JSON(v any) {
	err := json.NewEncoder(c.Response).Encode(v)
	if err != nil {
		c.Err(ErrISE)
		return
	}
	c.log()
}

func (c *Context) Redirect(url string, code int) {
	c.Code = code
	http.Redirect(c.Response, c.Request, url, code)
	c.log()
}

// Err is a HTTPErr wrapper for the custom Error type
func (c *Context) Err(err Error) {
	c.HTTPErr(err.JSON(), err.Code())
}

// HTTPErr is a http.Error wrapper
func (c *Context) HTTPErr(err string, code int) {
	c.Code = code
	http.Error(c.Response, err, code)
	c.log()
}

type CtxHandler func(*Context)
