// Package route provides structures and functions to represent a route.
package route

import (
	"fmt"
	"net/http"
	"net/http/httputil"

	"github.com/pingcap/log"
	"github.com/tidbcloud/clusterproxy/resolve"
	"github.com/tidbcloud/clusterproxy/util/cluster"
	"go.uber.org/zap"
	"k8s.io/client-go/kubernetes"
	v1 "k8s.io/client-go/listers/core/v1"
)

// Context represents the context required by a Route.
type Context struct {
	Cli       kubernetes.Interface
	SvcLister v1.ServiceLister
}

// Resolver resolves the requested cluster and the service route from the request
type Resolver interface {
	Resolve(*http.Request) (resolve.Cluster, string, error)
	Prefix() string
}

// Route represents a route.
type Route struct {
	Name       string                                       `json:"name"`    // Name of the route
	Version    Version                                      `json:"version"` // Version of the route
	Schema     *Schema                                      `json:"schema"`  // Schema of the route
	Path       string                                       `json:"path"`    // Path of the route
	GetHandler func(*Context, resolve.Cluster) http.Handler `json:"-"`       // Handler get function with the given context
}

// WithValidation returns a handler that validates the requests and trim the route path off the request path.
func (r *Route) WithValidation(c *Context, cl resolve.Cluster) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if err := r.Schema.Validate(req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		r.GetHandler(c, cl).ServeHTTP(w, req)
	})
}

func KubernetesServiceProxy(scheme, namespace, name, port string) *httputil.ReverseProxy {
	url := cluster.ServiceURL(scheme, namespace, name, port)
	proxy := httputil.NewSingleHostReverseProxy(url)
	proxy.ModifyResponse = setCORSHeader
	proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
		w.WriteHeader(http.StatusBadGateway)
		log.Error(fmt.Sprintf("Error while proxying the request %+v", err), zap.String("URL", r.URL.Path), zap.Error(err))
		// The first log statement has a stack trace.
		// This log statement is easier to look at if you don't need the stack.
		log.Error("Error while proxying the request", zap.String("URL", r.URL.Path), zap.Error(err))
	}
	return proxy
}

// setCORSHeader sets the CORS headers on the response.
func setCORSHeader(resp *http.Response) error {
	resp.Header.Set("Access-Control-Allow-Headers", "accept, content-type, authorization")
	resp.Header.Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	resp.Header.Set("Access-Control-Allow-Origin", "*")
	return nil
}
