package http_binding

import (
	"errors"
	"git.sxidc.com/go-tools/api_binding/http_binding/binding_context"
	"git.sxidc.com/go-tools/api_binding/http_binding/middleware"
	"git.sxidc.com/go-tools/api_binding/http_binding/request"
	"git.sxidc.com/go-tools/api_binding/http_binding/response"
	"git.sxidc.com/go-tools/api_binding/utils"
	"git.sxidc.com/service-supports/fserr"
	"git.sxidc.com/service-supports/fslog"
	"github.com/gin-gonic/gin"
	"net/http"
	"reflect"
	"strings"
)

type BusinessFunc[I any, O any] func(c *binding_context.Context, inputModel I) (O, error)
type BindingFunc[O any] func(c *binding_context.Context, request any, sendFunc response.SendFunc[O]) bool

type Binding struct {
	routerGroup *gin.RouterGroup
}

// NewBinding 创建版本对应的binding
func NewBinding(apiVersion string, middlewares ...middleware.Func) *Binding {
	apiPrefix := urlPrefix + "/api"
	if utils.IsStringNotEmpty(apiVersion) && apiVersion != "root" {
		apiPrefix += "/" + apiVersion
	}

	// 把binding.Middleware转换为gin的中间件函数
	ginMiddlewares := make([]gin.HandlerFunc, 0)
	for _, m := range middlewares {
		innerM := m
		ginMiddlewares = append(ginMiddlewares, func(c *gin.Context) {
			innerM(&binding_context.Context{Context: c})
		})
	}

	// 返回创建的路由组
	return &Binding{routerGroup: routerInstance.Group(apiPrefix, ginMiddlewares...)}
}

type HandleFunc func(c *binding_context.Context)

func (binding *Binding) AddHandler(method string, relativePath string, handleFunctions ...HandleFunc) error {
	if utils.IsStringEmpty(method) {
		return errors.New("没有传递方法名")
	}

	if utils.IsStringEmpty(relativePath) {
		return errors.New("没有传递相对路径")
	}

	if handleFunctions == nil || len(handleFunctions) == 0 {
		return errors.New("没有传递处理函数")
	}

	routeInfos := routerInstance.Routes()
	for _, routeInfo := range routeInfos {
		if routeInfo.Method == method && strings.TrimPrefix(routeInfo.Path, binding.routerGroup.BasePath()+"/") == relativePath {
			fslog.Error("添加的路径已存在: " + routeInfo.Path)
			return nil
		}
	}

	// 给单个路由增加中间件
	ginHandleFunctions := make([]gin.HandlerFunc, 0)
	for _, handleFunction := range handleFunctions {
		innerFunction := handleFunction
		ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) {
			innerFunction(&binding_context.Context{Context: c})
		})
	}

	binding.routerGroup.Handle(method, relativePath, ginHandleFunctions...)

	return nil
}

func PostBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) {
	item.bind(b.routerGroup, http.MethodPost, middlewares...)
}

func DeleteBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) {
	item.bind(b.routerGroup, http.MethodDelete, middlewares...)
}

func PutBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) {
	item.bind(b.routerGroup, http.MethodPut, middlewares...)
}

func GetBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) {
	item.bind(b.routerGroup, http.MethodGet, middlewares...)
}

func Bind[I any, O any](b *Binding, item *BindItem[I, O], middlewares ...middleware.Func) {
	item.bind(b.routerGroup, middlewares...)
}

func Static(b *Binding, item *StaticBindItem) {
	item.bind(b.routerGroup)
}

func StaticFile(b *Binding, item *StaticFileBindItem) {
	item.bind(b.routerGroup)
}

// SimpleBindItem  路由条目
type SimpleBindItem[I any, O any] struct {
	Path                string               // 请求路径
	ResponseFunc        response.SendFunc[O] // 响应泛型函数
	BusinessFunc        BusinessFunc[I, O]   // 业务泛型函数
	OptionalBindingFunc BindingFunc[O]       // 可选的绑定函数
}

func (item *SimpleBindItem[I, O]) bind(routerGroup *gin.RouterGroup, method string, middlewares ...middleware.Func) {
	bindingItem := &BindItem[I, O]{
		Method:         method,
		SimpleBindItem: item,
	}

	bindingItem.bind(routerGroup, middlewares...)
}

// BindItem 路由条目结构
type BindItem[I any, O any] struct {
	Method string
	*SimpleBindItem[I, O]
}

func (item *BindItem[I, O]) bind(routerGroup *gin.RouterGroup, middlewares ...middleware.Func) {
	if utils.IsStringEmpty(item.Path) {
		panic("需要指定路径")
	}

	if utils.IsStringEmpty(item.Method) {
		panic("需要指定方法")
	}

	if item.ResponseFunc == nil {
		panic("需要指定响应函数")
	}

	var inputCheckModel I
	inputType := reflect.TypeOf(inputCheckModel)
	if inputType != nil {
		if inputType.Kind() == reflect.Pointer {
			panic("输入对象不能使用指针类型")
		}

		if inputType.Kind() != reflect.Struct {
			panic("输入对象必须是结构")
		}
	}

	// 给单个路由增加中间件
	ginHandleFunctions := make([]gin.HandlerFunc, 0)
	for _, m := range middlewares {
		innerM := m
		ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) {
			innerM(&binding_context.Context{Context: c})
		})
	}

	ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) {
		bindingContext := &binding_context.Context{Context: c}
		var inputModel I

		// 请求的结构类型不为any
		if inputType != nil {
			// 将请求数据解析到inputModel中
			if item.OptionalBindingFunc != nil {
				ok := item.OptionalBindingFunc(bindingContext, &inputModel, item.ResponseFunc)
				if !ok {
					return
				}
			} else {
				switch item.Method {
				case http.MethodPost:
					fallthrough
				case http.MethodPut:
					ok := request.BindingJson(bindingContext, &inputModel, item.ResponseFunc)
					if !ok {
						return
					}
				case http.MethodGet:
					fallthrough
				case http.MethodDelete:
					ok := request.BindingQuery(bindingContext, &inputModel, item.ResponseFunc)
					if !ok {
						return
					}
				}
			}
		}

		// 执行业务函数
		if item.BusinessFunc != nil {
			statusCode := http.StatusOK
			outputModel, err := item.BusinessFunc(bindingContext, inputModel)
			if err != nil {
				statusCode = fserr.ParseCode(err).HttpCode
			}

			item.ResponseFunc(bindingContext, statusCode, outputModel, err)
			return
		}
	})

	// 所有的函数加入到执行链中
	routerGroup.Handle(item.Method, item.Path, ginHandleFunctions...)
}

// StaticBindItem 静态路由item
type StaticBindItem struct {
	RelativePath string
	Root         string
	WithBasePath bool
}

func (item *StaticBindItem) bind(routerGroup *gin.RouterGroup) {
	if item.WithBasePath {
		routerGroup.Static(strings.TrimPrefix(item.RelativePath, routerGroup.BasePath()), item.Root)
	} else {
		routerGroup.Static(item.RelativePath, item.Root)
	}
}

type StaticFileBindItem struct {
	RelativePath string
	FilePath     string
	WithBasePath bool
}

func (item *StaticFileBindItem) bind(routerGroup *gin.RouterGroup) {
	if item.WithBasePath {
		routerGroup.StaticFile(strings.TrimPrefix(item.RelativePath, routerGroup.BasePath()), item.FilePath)
	} else {
		routerGroup.StaticFile(item.RelativePath, item.FilePath)
	}
}