package ginext

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"runtime/debug"
)

type PreContext struct {
	ginCtx  *gin.Context
	wrapper *GinWrapper
	uri     any
	query   any
	body    any
	form    any
	header  any
}

func (pctx *PreContext) URI(uri any) *PreContext {
	pctx.uri = uri
	return pctx
}

func (pctx *PreContext) Query(query any) *PreContext {
	pctx.query = query
	return pctx
}

func (pctx *PreContext) Body(body any) *PreContext {
	pctx.body = body
	return pctx
}

func (pctx *PreContext) Form(form any) *PreContext {
	pctx.form = form
	return pctx
}

func (pctx *PreContext) Header(header any) *PreContext {
	pctx.header = header
	return pctx
}

func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
	if pctx.uri != nil {
		if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
			err = exerr.Wrap(err, "Failed to read uri").
				WithType(exerr.TypeBindFailURI).
				Str("struct_type", fmt.Sprintf("%T", pctx.uri)).
				Build()
			return nil, nil, langext.Ptr(Error(err))
		}
	}

	if pctx.query != nil {
		if err := pctx.ginCtx.ShouldBindQuery(pctx.query); err != nil {
			err = exerr.Wrap(err, "Failed to read query").
				WithType(exerr.TypeBindFailQuery).
				Str("struct_type", fmt.Sprintf("%T", pctx.query)).
				Build()
			return nil, nil, langext.Ptr(Error(err))
		}
	}

	if pctx.body != nil {
		if pctx.ginCtx.ContentType() == "application/json" {
			if err := pctx.ginCtx.ShouldBindJSON(pctx.body); err != nil {
				err = exerr.Wrap(err, "Failed to read json-body").
					WithType(exerr.TypeBindFailJSON).
					Str("struct_type", fmt.Sprintf("%T", pctx.body)).
					Build()
				return nil, nil, langext.Ptr(Error(err))
			}
		} else {
			err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body").
				Str("struct_type", fmt.Sprintf("%T", pctx.body)).
				Build()
			return nil, nil, langext.Ptr(Error(err))
		}
	}

	if pctx.form != nil {
		if pctx.ginCtx.ContentType() == "multipart/form-data" {
			if err := pctx.ginCtx.ShouldBindWith(pctx.form, binding.Form); err != nil {
				err = exerr.Wrap(err, "Failed to read multipart-form").
					WithType(exerr.TypeBindFailFormData).
					Str("struct_type", fmt.Sprintf("%T", pctx.form)).
					Build()
				return nil, nil, langext.Ptr(Error(err))
			}
		} else {
			err := exerr.New(exerr.TypeBindFailFormData, "missing form body").
				Str("struct_type", fmt.Sprintf("%T", pctx.form)).
				Build()
			return nil, nil, langext.Ptr(Error(err))
		}
	}

	if pctx.header != nil {
		if err := pctx.ginCtx.ShouldBindHeader(pctx.query); err != nil {
			err = exerr.Wrap(err, "Failed to read header").
				WithType(exerr.TypeBindFailHeader).
				Str("struct_type", fmt.Sprintf("%T", pctx.query)).
				Build()
			return nil, nil, langext.Ptr(Error(err))
		}
	}

	ictx, cancel := context.WithTimeout(context.Background(), pctx.wrapper.requestTimeout)
	actx := CreateAppContext(pctx.ginCtx, ictx, cancel)

	return actx, pctx.ginCtx, nil
}

func callPanicSafe(fn WHandlerFunc, pctx PreContext) (res HTTPResponse, stackTrace string, panicObj any) {
	defer func() {
		if rec := recover(); rec != nil {
			res = nil
			stackTrace = string(debug.Stack())
			panicObj = rec
		}
	}()

	res = fn(pctx)
	return res, "", nil
}