package ginext

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	json "gogs.mikescher.com/BlackForestBytes/goext/gojson"
)

type headerval struct {
	Key string
	Val string
}

type HTTPResponse interface {
	Write(g *gin.Context)
	WithHeader(k string, v string) HTTPResponse
	IsSuccess() bool
}

type jsonHTTPResponse struct {
	statusCode int
	data       any
	headers    []headerval
}

func (j jsonHTTPResponse) Write(g *gin.Context) {
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	var f *string
	if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" {
		f = &jsonfilter
	}
	g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f})
}

func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j jsonHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type emptyHTTPResponse struct {
	statusCode int
	headers    []headerval
}

func (j emptyHTTPResponse) Write(g *gin.Context) {
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	g.Status(j.statusCode)
}

func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j emptyHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type textHTTPResponse struct {
	statusCode int
	data       string
	headers    []headerval
}

func (j textHTTPResponse) Write(g *gin.Context) {
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	g.String(j.statusCode, "%s", j.data)
}

func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j textHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type dataHTTPResponse struct {
	statusCode  int
	data        []byte
	contentType string
	headers     []headerval
}

func (j dataHTTPResponse) Write(g *gin.Context) {
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	g.Data(j.statusCode, j.contentType, j.data)
}

func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j dataHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type fileHTTPResponse struct {
	mimetype string
	filepath string
	filename *string
	headers  []headerval
}

func (j fileHTTPResponse) Write(g *gin.Context) {
	g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later...
	if j.filename != nil {
		g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename))

	}
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	g.File(j.filepath)
}

func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j fileHTTPResponse) IsSuccess() bool {
	return true
}

type downloadDataHTTPResponse struct {
	statusCode int
	mimetype   string
	data       []byte
	filename   *string
	headers    []headerval
}

func (j downloadDataHTTPResponse) Write(g *gin.Context) {
	g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later...
	if j.filename != nil {
		g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename))

	}
	for _, v := range j.headers {
		g.Header(v.Key, v.Val)
	}
	g.Data(j.statusCode, j.mimetype, j.data)
}

func (j downloadDataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j downloadDataHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type redirectHTTPResponse struct {
	statusCode int
	url        string
	headers    []headerval
}

func (j redirectHTTPResponse) Write(g *gin.Context) {
	g.Redirect(j.statusCode, j.url)
}

func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j redirectHTTPResponse) IsSuccess() bool {
	return j.statusCode >= 200 && j.statusCode <= 399
}

type jsonAPIErrResponse struct {
	err     *exerr.ExErr
	headers []headerval
}

func (j jsonAPIErrResponse) Write(g *gin.Context) {
	j.err.Output(g)
}

func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse {
	j.headers = append(j.headers, headerval{k, v})
	return j
}

func (j jsonAPIErrResponse) IsSuccess() bool {
	return false
}

func Status(sc int) HTTPResponse {
	return &emptyHTTPResponse{statusCode: sc}
}

func JSON(sc int, data any) HTTPResponse {
	return &jsonHTTPResponse{statusCode: sc, data: data}
}

func Data(sc int, contentType string, data []byte) HTTPResponse {
	return &dataHTTPResponse{statusCode: sc, contentType: contentType, data: data}
}

func Text(sc int, data string) HTTPResponse {
	return &textHTTPResponse{statusCode: sc, data: data}
}

func File(mimetype string, filepath string) HTTPResponse {
	return &fileHTTPResponse{mimetype: mimetype, filepath: filepath}
}

func Download(mimetype string, filepath string, filename string) HTTPResponse {
	return &fileHTTPResponse{mimetype: mimetype, filepath: filepath, filename: &filename}
}

func DownloadData(status int, mimetype string, filename string, data []byte) HTTPResponse {
	return &downloadDataHTTPResponse{statusCode: status, mimetype: mimetype, data: data, filename: &filename}
}

func Redirect(sc int, newURL string) HTTPResponse {
	return &redirectHTTPResponse{statusCode: sc, url: newURL}
}

func Error(e error) HTTPResponse {
	return &jsonAPIErrResponse{
		err: exerr.FromError(e),
	}
}

func ErrWrap(e error, errorType exerr.ErrorType, msg string) HTTPResponse {
	return &jsonAPIErrResponse{
		err: exerr.FromError(exerr.Wrap(e, msg).WithType(errorType).Build()),
	}
}

func NotImplemented() HTTPResponse {
	return Error(exerr.New(exerr.TypeNotImplemented, "").Build())
}