/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 */
package router

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strconv"
)

type Context struct {
	Ctx        context.Context
	Request    *http.Request
	Writer     http.ResponseWriter
	PathMap    map[string]string
	Bools      map[string]bool
	Strings    map[string]string
	StatusCode int
}

func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
	rctx := &Context{
		Writer:  writer,
		Request: request,
		Ctx:     request.Context(),
		PathMap: make(map[string]string),
		Bools:   make(map[string]bool),
		Strings: make(map[string]string),
	}
	return rctx
}

// Aux maps
func (rctx *Context) SetBool(key string, value bool) {
	rctx.Bools[key] = value
}

func (rctx *Context) GetBool(key string) (bool, bool) {
	exists, value := rctx.Bools[key]
	return exists, value
}

func (rctx *Context) SetString(key string, value string) {
	rctx.Strings[key] = value
}

func (rctx *Context) GetString(key string) (string, bool) {
	value, exists := rctx.Strings[key]
	return value, exists
}

// Request
func (rctx *Context) GetSubpath(key string) (string, bool) {
	value, exists := rctx.PathMap[key]
	return value, exists
}

func (rctx *Context) URL() *url.URL {
	return rctx.Request.URL
}

func (rctx *Context) GetQuery(key string) string {
	return rctx.Request.URL.Query().Get(key)
}

func (rctx *Context) GetHeader(key string) string {
	return rctx.Request.Header.Get(key)
}

func (rctx *Context) GetHeaders() http.Header {
	return rctx.Request.Header
}

func (rctx *Context) GetContext() context.Context {
	return rctx.Request.Context()
}

// Binding
const emptyJSON = "{}"

func (rctx *Context) BindJSON(obj any) error {
	buffer := bytes.NewBuffer(nil)
	_, err := io.Copy(buffer, rctx.Request.Body)
	if err != nil {
		return err
	}
	reqBody := buffer.Bytes()
	if len(reqBody) == 0 {
		reqBody = []byte(emptyJSON)
	}
	err = json.Unmarshal(reqBody, obj)
	if err != nil {
		return err
	}
	return err
}

func (rctx *Context) BindQuery(obj any) error {
	qMap := make(map[string]string)
	for key, val := range rctx.Request.URL.Query() {
		if len(val) == 1 {
			qMap[key] = val[0]
		}
	}
	return bindObj(obj, qMap, "param")
}

// Response
func (rctx *Context) SetHeader(key, value string) {
	rctx.Writer.Header().Set(key, value)
}

func (rctx *Context) SetStatus(httpStatus int) {
	rctx.StatusCode = httpStatus
	rctx.Writer.WriteHeader(httpStatus)
}

func (rctx *Context) SendJSON(statusCode int, payload any) {
	rctx.StatusCode = statusCode
	buffer := bytes.NewBuffer(nil)
	json.NewEncoder(buffer).Encode(payload)
	rctx.Writer.Header().Set("Content-Type", "application/json")
	size := strconv.FormatInt(int64(len(buffer.Bytes())), 10)
	rctx.Writer.Header().Set("Content-Length", size)
	rctx.Writer.WriteHeader(statusCode)
	rctx.Writer.Write(buffer.Bytes())
}

func (rctx *Context) SendText(statusCode int, payload string) {
	rctx.StatusCode = statusCode
	size := strconv.FormatInt(int64(len(payload)), 10)
	rctx.Writer.Header().Set("Content-Type", "text/plain")
	rctx.Writer.Header().Set("Content-Length", size)
	rctx.Writer.WriteHeader(statusCode)
	rctx.Writer.Write([]byte(payload))
}

func (rctx *Context) SendBytes(statusCode int, ctype string, payload []byte) {
	rctx.StatusCode = statusCode
	size := strconv.FormatInt(int64(len(payload)), 10)
	rctx.Writer.Header().Set("Content-Type", ctype)
	rctx.Writer.Header().Set("Content-Length", size)
	rctx.Writer.WriteHeader(statusCode)
	rctx.Writer.Write(payload)
}
