package api

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"regexp"
	"strconv"

	"github.com/pingcap/dbaas/central/config"
	"github.com/pingcap/dbaas/central/db/tenant"
	"github.com/pingcap/dbaas/pkg/auth/authuser"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
)

type allowListItemType struct {
	Method  string
	PathReg *regexp.Regexp
}

var AllowLists = []allowListItemType{
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/invoices.*`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/payments/stripe/.*`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/costexplorer.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/submited_subcriptions.*`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/subcriptions/.*/actions/accept`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/subcriptions/.*/actions/reject`)},
}

func validatePath(path string, method string, tenantID uint64, allowLists []allowListItemType) bool {
	for _, allowListItem := range allowLists {
		allowListMethod, allowListPath := allowListItem.Method, allowListItem.PathReg
		if !(allowListMethod == "ANY" || allowListMethod == method) {
			continue
		}
		if !allowListPath.MatchString(path) {
			continue
		}
		if index := allowListPath.SubexpIndex("tenant_id"); index != -1 {
			res := allowListPath.FindStringSubmatch(path)
			if res[index] != strconv.FormatInt(int64(tenantID), 10) {
				continue
			}
		}
		return true
	}
	return false
}

func validateProxyPath(path string, method string, user *tenant.User) error {
	if validatePath(path, method, user.TenantID.Uint64(), AllowLists) {
		return nil
	}
	return errors.New("banned billing proxy path")
}

// must use http.HandlerFunc to adapt server main middleware

func BillingProxy(w http.ResponseWriter, req *http.Request) {
	proxyPath := req.URL.Path

	target, err := url.Parse(config.Global().BillingConfig.Billing2BaseURL)
	if err != nil {
		authErr := errcode.NewInternalErr(errors.New("proxy error: billing address error"))
		code := errcode.CodeChain(authErr)
		http.Error(w, authErr.Error(), code.Code().HTTPCode())
		return
	}

	user, err := authuser.GetUserFromContext(req.Context())
	if err != nil {
		// should tested by middleware, so a internal server error
		authErr := errcode.NewInternalErr(errors.New("proxy error: auth error"))
		code := errcode.CodeChain(authErr)
		http.Error(w, authErr.Error(), code.Code().HTTPCode())
		return
	}

	if err := validateProxyPath(proxyPath, req.Method, user); err != nil {
		authErr := errcode.NewInvalidInputErr(errors.Annotate(err, "proxy error"))
		code := errcode.CodeChain(authErr)
		http.Error(w, authErr.Error(), code.Code().HTTPCode())
		return
	}

	director := func(req *http.Request) {
		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host
		req.URL.Path = proxyPath
	}
	proxy := &httputil.ReverseProxy{Director: director}
	proxy.ServeHTTP(w, req)
}
