package bunrouter

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

type routeCtxKey struct{}

// ParamsFromContext retrieves route parameters from the given context.
// It returns an empty Params if no parameters are found.
func ParamsFromContext(ctx context.Context) Params {
	if ctx == nil {
		return Params{}
	}
	route, _ := ctx.Value(routeCtxKey{}).(Params)
	return route
}

// contextWithParams stores route parameters in the context.
func contextWithParams(ctx context.Context, params Params) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, routeCtxKey{}, params)
}

//------------------------------------------------------------------------------

// HTTPHandler converts http.Handler to bunrouter.HandlerFunc.
func HTTPHandler(handler http.Handler) HandlerFunc {
	if handler == nil {
		panic("bunrouter: nil handler")
	}
	return HTTPHandlerFunc(handler.ServeHTTP)
}

// HTTPHandlerFunc converts http.HandlerFunc to bunrouter.HandlerFunc.
func HTTPHandlerFunc(handler http.HandlerFunc) HandlerFunc {
	if handler == nil {
		panic("bunrouter: nil handler")
	}

	return func(w http.ResponseWriter, req Request) (err error) {
		if w == nil {
			return fmt.Errorf("bunrouter: nil response writer")
		}

		ctx := contextWithParams(req.Context(), req.params)

		defer func() {
			if v := recover(); v != nil {
				var ok bool
				err, ok = v.(error)
				if !ok {
					err = fmt.Errorf("bunrouter: panic recovered: %v", v)
				}
			}
		}()

		handler.ServeHTTP(w, req.Request.WithContext(ctx))

		return err
	}
}

// HandlerFunc is a function that handles HTTP requests in bunrouter.
// It returns an error that will be handled by the router.
type HandlerFunc func(w http.ResponseWriter, req Request) error

var _ http.Handler = (*HandlerFunc)(nil)

func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h == nil {
		http.Error(w, "Handler not found", http.StatusInternalServerError)
		return
	}

	if req == nil {
		http.Error(w, "Invalid request", http.StatusBadRequest)
		return
	}

	if err := h(w, NewRequest(req)); err != nil {
		code := http.StatusInternalServerError
		if httpErr, ok := err.(HTTPError); ok {
			code = httpErr.StatusCode()
		}
		http.Error(w, err.Error(), code)
	}
}

// HTTPError represents an HTTP error with a status code
type HTTPError interface {
	error
	StatusCode() int
}

// MiddlewareFunc is a function that wraps a HandlerFunc to provide middleware functionality.
type MiddlewareFunc func(next HandlerFunc) HandlerFunc

//------------------------------------------------------------------------------

// Request extends http.Request with route parameters.
type Request struct {
	*http.Request
	params Params
}

// NewRequest creates a new Request instance from an http.Request.
func NewRequest(req *http.Request) Request {
	if req == nil {
		req = &http.Request{}
	}
	return Request{
		Request: req,
		params:  ParamsFromContext(req.Context()),
	}
}

func newRequestParams(req *http.Request, params Params) Request {
	if req == nil {
		req = &http.Request{}
	}
	return Request{
		Request: req,
		params:  params,
	}
}

// WithContext returns a new Request with the provided context.
func (req Request) WithContext(ctx context.Context) Request {
	if ctx == nil {
		ctx = context.Background()
	}
	return Request{
		Request: req.Request.WithContext(ctx),
		params:  req.params,
	}
}

// Params returns the route parameters associated with the request.
func (req Request) Params() Params {
	return req.params
}

// Param returns the value of the named parameter or empty string if not found.
func (req Request) Param(key string) string {
	return req.Params().ByName(key)
}

// Route returns the matched route pattern.
func (req Request) Route() string {
	return req.Params().Route()
}

//------------------------------------------------------------------------------

// Params holds route parameters and route information.
type Params struct {
	path        string
	node        *node
	handler     *routeHandler
	wildcardLen uint16
}

// IsZero returns true if Params has no associated route node.
func (ps Params) IsZero() bool {
	return ps.node == nil
}

// Route returns the route pattern that matched the request.
func (ps Params) Route() string {
	if ps.node != nil {
		return ps.node.route
	}
	return ""
}

// Get returns the value of the named parameter and whether it was found.
func (ps Params) Get(name string) (string, bool) {
	if ps.node == nil || ps.handler == nil {
		return "", false
	}
	if i, ok := ps.handler.params[name]; ok {
		return ps.findParam(i)
	}
	return "", false
}

func (ps *Params) findParam(paramIndex int) (string, bool) {
	if ps.node == nil || ps.handler == nil {
		return "", false
	}

	path := ps.path
	pathLen := len(path)
	if pathLen == 0 {
		return "", false
	}

	currNode := ps.node
	currParamIndex := len(ps.handler.params) - 1

	if paramIndex < 0 || paramIndex > currParamIndex {
		return "", false
	}

	// Wildcard can be only in the final node.
	if ps.node.isWC {
		if currParamIndex == paramIndex {
			if int(ps.wildcardLen) > pathLen {
				return "", false
			}
			pathLen -= int(ps.wildcardLen)
			return path[pathLen:], true
		}

		currParamIndex--
		if int(ps.wildcardLen) > pathLen {
			return "", false
		}
		pathLen -= int(ps.wildcardLen)
		path = path[:pathLen]
	}

	for currNode != nil {
		if currNode.part[0] != ':' { // static node
			partLen := len(currNode.part)
			if partLen > pathLen {
				return "", false
			}
			pathLen -= partLen
			path = path[:pathLen]
			currNode = currNode.parent
			continue
		}

		i := strings.LastIndexByte(path, '/')
		if i == -1 {
			return "", false
		}
		pathLen = i + 1

		if currParamIndex == paramIndex {
			return path[pathLen:], true
		}

		currParamIndex--
		path = path[:pathLen]
		currNode = currNode.parent
	}

	return "", false
}

// ByName returns the value of the named parameter or empty string if not found.
func (ps Params) ByName(name string) string {
	s, _ := ps.Get(name)
	return s
}

// Int parses the named parameter as an integer.
func (ps Params) Int(name string) (int, error) {
	value := ps.ByName(name)
	if value == "" {
		return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
	}
	return strconv.Atoi(value)
}

// Uint32 parses the named parameter as an unsigned 32-bit integer.
func (ps Params) Uint32(name string) (uint32, error) {
	value := ps.ByName(name)
	if value == "" {
		return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
	}
	n, err := strconv.ParseUint(value, 10, 32)
	return uint32(n), err
}

// Uint64 parses the named parameter as an unsigned 64-bit integer.
func (ps Params) Uint64(name string) (uint64, error) {
	value := ps.ByName(name)
	if value == "" {
		return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
	}
	return strconv.ParseUint(value, 10, 64)
}

// Int32 parses the named parameter as a signed 32-bit integer.
func (ps Params) Int32(name string) (int32, error) {
	value := ps.ByName(name)
	if value == "" {
		return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
	}
	n, err := strconv.ParseInt(value, 10, 32)
	return int32(n), err
}

// Int64 parses the named parameter as a signed 64-bit integer.
func (ps Params) Int64(name string) (int64, error) {
	value := ps.ByName(name)
	if value == "" {
		return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
	}
	return strconv.ParseInt(value, 10, 64)
}

// Map returns route parameters as a map[string]string.
func (ps Params) Map() map[string]string {
	if ps.handler == nil || len(ps.handler.params) == 0 {
		return make(map[string]string)
	}
	m := make(map[string]string, len(ps.handler.params))
	for param, index := range ps.handler.params {
		if value, ok := ps.findParam(index); ok {
			m[param] = value
		}
	}
	return m
}

// Param represents a key-value pair of route parameters.
type Param struct {
	Key   string
	Value string
}

// Slice returns route parameters as a slice of Param.
func (ps Params) Slice() []Param {
	if ps.handler == nil || len(ps.handler.params) == 0 {
		return []Param{}
	}
	slice := make([]Param, len(ps.handler.params))
	for param, index := range ps.handler.params {
		if value, ok := ps.findParam(index); ok {
			slice[index] = Param{Key: param, Value: value}
		}
	}
	return slice
}

//------------------------------------------------------------------------------

// H is a shorthand for map[string]interface{}.
type H map[string]interface{}

// JSON marshals the value as JSON and writes it to the response writer.
// It sets the Content-Type header to application/json.
//
// Don't hesitate to copy-paste this function to your project and customize it as necessary.
func JSON(w http.ResponseWriter, value interface{}) error {
	if w == nil {
		return fmt.Errorf("bunrouter: nil response writer")
	}

	w.Header().Set("Content-Type", "application/json")

	if value == nil {
		if _, err := w.Write([]byte("null")); err != nil {
			return fmt.Errorf("bunrouter: failed to write null response: %w", err)
		}
		return nil
	}

	enc := json.NewEncoder(w)
	if err := enc.Encode(value); err != nil {
		return fmt.Errorf("bunrouter: JSON encoding error: %w", err)
	}

	return nil
}
