package api

import (
	"fmt"
	"net/http"
)

type HandlerFilters struct {
	filters        []Filter
	defaultHandler http.Handler
}

type Filter struct {
	queries []Pair
	headers []Pair
	h       http.Handler
}

type Pair struct {
	Key   string
	Value string
}

func NewHandlerFilter() *HandlerFilters {
	return &HandlerFilters{}
}

func NewFilter() *Filter {
	return &Filter{}
}

func (hf *HandlerFilters) Add(filter *Filter) *HandlerFilters {
	hf.filters = append(hf.filters, *filter)
	return hf
}

// HeadersMatch adds a matcher for header values.
// It accepts a sequence of key/value pairs. Values may define variables.
// Panics if number of parameters is not even.
// Supports only exact matching.
// If the value is an empty string, it will match any value if the key is set.
func (f *Filter) HeadersMatch(pairs ...string) *Filter {
	length := len(pairs)
	if length%2 != 0 {
		panic(fmt.Errorf("filter headers: number of parameters must be multiple of 2, got %v", pairs))
	}

	for i := 0; i < length; i += 2 {
		f.headers = append(f.headers, Pair{
			Key:   pairs[i],
			Value: pairs[i+1],
		})
	}

	return f
}

// Headers is similar to HeadersMatch but accept only header keys, set value to empty string internally.
func (f *Filter) Headers(headers ...string) *Filter {
	for _, header := range headers {
		f.headers = append(f.headers, Pair{
			Key:   header,
			Value: "",
		})
	}

	return f
}

func (f *Filter) Handler(handler http.HandlerFunc) *Filter {
	f.h = handler
	return f
}

// QueriesMatch adds a matcher for URL query values.
// It accepts a sequence of key/value pairs. Values may define variables.
// Panics if number of parameters is not even.
// Supports only exact matching.
// If the value is an empty string, it will match any value if the key is set.
func (f *Filter) QueriesMatch(pairs ...string) *Filter {
	length := len(pairs)
	if length%2 != 0 {
		panic(fmt.Errorf("filter headers: number of parameters must be multiple of 2, got %v", pairs))
	}

	for i := 0; i < length; i += 2 {
		f.queries = append(f.queries, Pair{
			Key:   pairs[i],
			Value: pairs[i+1],
		})
	}

	return f
}

// Queries is similar to QueriesMatch but accept only query keys, set value to empty string internally.
func (f *Filter) Queries(queries ...string) *Filter {
	for _, query := range queries {
		f.queries = append(f.queries, Pair{
			Key:   query,
			Value: "",
		})
	}

	return f
}

func (hf *HandlerFilters) DefaultHandler(handler http.HandlerFunc) *HandlerFilters {
	hf.defaultHandler = handler
	return hf
}

func (hf *HandlerFilters) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if handler := hf.match(r); handler != nil {
		handler.ServeHTTP(w, r)
		return
	}

	hf.defaultHandler.ServeHTTP(w, r)
}

func (hf *HandlerFilters) match(r *http.Request) http.Handler {
LOOP:
	for _, filter := range hf.filters {
		for _, header := range filter.headers {
			hdrVals := r.Header.Values(header.Key)
			if len(hdrVals) == 0 || header.Value != "" && header.Value != hdrVals[0] {
				continue LOOP
			}
		}
		for _, query := range filter.queries {
			queryVal := r.URL.Query().Get(query.Key)
			if !r.URL.Query().Has(query.Key) || query.Value != "" && query.Value != queryVal {
				continue LOOP
			}
		}
		return filter.h
	}

	return nil
}