package http

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

	"github.com/m1k1o/neko/server/pkg/types"
	"github.com/m1k1o/neko/server/pkg/utils"
)

type BatchRequest struct {
	Path   string          `json:"path"`
	Method string          `json:"method"`
	Body   json.RawMessage `json:"body,omitempty"`
}

type BatchResponse struct {
	Path   string          `json:"path"`
	Method string          `json:"method"`
	Body   json.RawMessage `json:"body,omitempty"`
	Status int             `json:"status"`
}

func (b *BatchResponse) Error(httpErr *utils.HTTPError) (err error) {
	b.Body, err = json.Marshal(httpErr)
	b.Status = httpErr.Code
	return
}

type batchHandler struct {
	Router     types.Router
	PathPrefix string
	Excluded   []string
}

func (b *batchHandler) Handle(w http.ResponseWriter, r *http.Request) error {
	var requests []BatchRequest
	if err := json.NewDecoder(r.Body).Decode(&requests); err != nil {
		return err
	}

	responses := make([]BatchResponse, len(requests))
	for i, request := range requests {
		res := BatchResponse{
			Path:   request.Path,
			Method: request.Method,
		}

		if !strings.HasPrefix(request.Path, b.PathPrefix) {
			res.Error(utils.HttpBadRequest("this path is not allowed in batch requests"))
			responses[i] = res
			continue
		}

		if exists, _ := utils.ArrayIn(request.Path, b.Excluded); exists {
			res.Error(utils.HttpBadRequest("this path is excluded from batch requests"))
			responses[i] = res
			continue
		}

		// prepare request
		req, err := http.NewRequest(request.Method, request.Path, bytes.NewBuffer(request.Body))
		if err != nil {
			return err
		}

		// copy headers
		for k, vv := range r.Header {
			for _, v := range vv {
				req.Header.Add(k, v)
			}
		}

		// execute request
		rr := newResponseRecorder()
		b.Router.ServeHTTP(rr, req)

		// read response
		body, err := io.ReadAll(rr.Body)
		if err != nil {
			return err
		}

		// write response
		responses[i] = BatchResponse{
			Path:   request.Path,
			Method: request.Method,
			Body:   body,
			Status: rr.Code,
		}
	}

	return utils.HttpSuccess(w, responses)
}

type responseRecorder struct {
	Code      int
	HeaderMap http.Header
	Body      *bytes.Buffer
}

func newResponseRecorder() *responseRecorder {
	return &responseRecorder{
		Code:      http.StatusOK,
		HeaderMap: make(http.Header),
		Body:      new(bytes.Buffer),
	}
}

func (w *responseRecorder) Header() http.Header {
	return w.HeaderMap
}

func (w *responseRecorder) Write(b []byte) (int, error) {
	return w.Body.Write(b)
}

func (w *responseRecorder) WriteHeader(code int) {
	w.Code = code
}
