// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
	"context"
	"fmt"
	"net/http"
	"path"
	"strings"
	"time"

	"yunion.io/x/onecloud/pkg/appsrv"
)

type handleFunc func(context.Context, http.ResponseWriter, *http.Request)

type SHandler struct {
	Method         string
	Prefix         string
	MiddlewareFunc appsrv.MiddlewareFunc
	HandleFunc     handleFunc
}

func NewHandler(method string, mf appsrv.MiddlewareFunc, hf handleFunc, paths ...string) SHandler {
	h := SHandler{
		Method:         method,
		MiddlewareFunc: mf,
		HandleFunc:     hf,
	}
	h.Prefix = path.Join(paths...)
	return h
}

func (h SHandler) indexKey() string {
	return fmt.Sprintf("%s-%s", h.Method, h.Prefix)
}

func (h SHandler) Bind(app *appsrv.Application) {
	f := h.HandleFunc
	if h.MiddlewareFunc != nil {
		f = h.MiddlewareFunc(f)
	}
	segs := appsrv.SplitPath(h.Prefix)
	hi := appsrv.NewHandlerInfo(h.Method, segs, f, nil, "", nil)
	if h.Method == "GET" {
		hi.SetProcessTimeoutCallback(func(hdr *appsrv.SHandlerInfo, r *http.Request) time.Duration {
			if len(r.URL.Query().Get("export_keys")) > 0 ||
				r.URL.Query().Has("force_no_paging") ||
				strings.HasSuffix(r.URL.Path, "/splitable-export") {
				return time.Hour * 2
			}
			return -time.Second
		})
	}
	app.AddHandler3(hi)
}

func NewMethodHandlerFactory(method string, mf appsrv.MiddlewareFunc, prefix string) func(handleFunc, ...string) SHandler {
	return func(hf handleFunc, paths ...string) SHandler {
		pPaths := []string{prefix}
		pPaths = append(pPaths, paths...)
		return NewHandler(method, mf, hf, pPaths...)
	}
}

type SHandlers struct {
	prefix   string
	handlers map[string]SHandler
}

func NewHandlers(prefix string) *SHandlers {
	return &SHandlers{
		prefix:   prefix,
		handlers: make(map[string]SHandler),
	}
}

type HandlerPath struct {
	f     handleFunc
	paths []string
}

func (f *SHandlers) GetPrefix() string {
	return f.prefix
}

func (f *SHandlers) AddByMethod(method string, mf appsrv.MiddlewareFunc, hs ...HandlerPath) *SHandlers {
	newH := NewMethodHandlerFactory(method, mf, f.prefix)
	for _, h := range hs {
		hd := newH(h.f, h.paths...)
		idx := hd.indexKey()
		f.handlers[idx] = hd
	}
	return f
}

func (f *SHandlers) Bind(app *appsrv.Application) {
	for _, h := range f.handlers {
		h.Bind(app)
	}
}

func NewHP(f handleFunc, paths ...string) HandlerPath {
	return HandlerPath{f, paths}
}

type IHandler interface {
	Bind(*appsrv.Application)
}
