package apiserv

import (
	"net/http"
	"regexp"
	"strings"

	"github.com/vladimirok5959/golang-utils/utils/http/logger"
)

var mParam = regexp.MustCompile(`\{([^/]*)}`)
var TestingMockParams func() []Param = nil

var mParams = &Params{
	list: map[*http.Request][]Param{},
}

type ServeMux struct {
	handlers *Handlers
}

func NewServeMux() *ServeMux {
	s := ServeMux{
		handlers: &Handlers{
			list: map[*regexp.Regexp]Handler{},
		},
	}
	return &s
}

func GetParams(r *http.Request) []Param {
	if TestingMockParams != nil {
		return TestingMockParams()
	}
	mParams.Lock()
	defer mParams.Unlock()
	if v, ok := mParams.list[r]; ok {
		return v
	}
	return []Param{}
}

func (s ServeMux) regexp(pattern string) string {
	pattern = strings.ReplaceAll(pattern, "-", "\\-")
	pattern = strings.ReplaceAll(pattern, ".", "\\.")
	pattern = strings.ReplaceAll(pattern, "/", "\\/")
	pattern = mParam.ReplaceAllStringFunc(pattern, func(str string) string {
		if str == "{i}" {
			return "([\\d]+)"
		} else if str == "{s}" {
			return "([^\\/]+)"
		}
		return str
	})
	return "(?i)^" + pattern + "$"
}

func (s ServeMux) Delete(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodDelete}, handler)
}

func (s ServeMux) Get(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodGet}, handler)
}

func (s ServeMux) Handle(pattern string, methods []string, handler http.Handler) {
	s.handlers.Lock()
	defer s.handlers.Unlock()
	if pattern != "" {
		re := regexp.MustCompile(s.regexp(pattern))
		s.handlers.list[re] = Handler{
			handler: handler,
			methods: methods,
		}
	} else {
		s.handlers.list[nil] = Handler{
			handler: handler,
			methods: methods,
		}
	}
}

func (s ServeMux) NotFound(handler http.Handler) {
	s.Handle("", []string{http.MethodGet}, handler)
}

func (s ServeMux) Options(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodOptions}, handler)
}

func (s ServeMux) Patch(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodPatch}, handler)
}

func (s ServeMux) Post(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodPost}, handler)
}

func (s ServeMux) Put(pattern string, handler http.Handler) {
	s.Handle(pattern, []string{http.MethodPut}, handler)
}

func (s ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.handlers.Lock()
	defer s.handlers.Unlock()
	for re, h := range s.handlers.list {
		if re != nil && re.Match([]byte(r.URL.Path)) {
			if h.IsMethod(r.Method) {
				if rs := re.FindAllStringSubmatch(r.URL.Path, 1); len(rs) >= 1 {
					defer func() {
						mParams.Lock()
						delete(mParams.list, r)
						defer mParams.Unlock()
					}()
					mParams.Lock()
					for _, p := range rs[0] {
						if _, ok := mParams.list[r]; !ok {
							mParams.list[r] = []Param{{Value: p}}
						} else {
							mParams.list[r] = append(mParams.list[r], Param{Value: p})
						}
						select {
						case <-r.Context().Done():
							mParams.Unlock()
							return
						default:
						}
					}
					mParams.Unlock()
				}
				select {
				case <-r.Context().Done():
					return
				default:
				}
				logger.LogRequests(h.handler).ServeHTTP(w, r)
				return
			} else {
				w.WriteHeader(http.StatusMethodNotAllowed)
				return
			}
		}
		select {
		case <-r.Context().Done():
			return
		default:
		}
	}

	// Error 404
	if h, ok := s.handlers.list[nil]; ok {
		logger.LogRequests(h.handler).ServeHTTP(w, r)
		return
	}

	w.WriteHeader(http.StatusNotFound)
}