package api

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

	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/thirdparty/billing2"
)

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: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/plans`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/payments.*`)},
	{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`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_info`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/support_plan`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/binded_support_plan`)},
	{Method: "DELETE", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/binded_support_plan/public`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/binded_support_plan/public`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/credits`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/credits_summary`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/credits/.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/cost_quota`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/contracts/\d+/download`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/users/contracts.*`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/users/contracts/\d+/actions/.*`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/switch/.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/users/discounts`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/budgets.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/invoices.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/payments.*`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/payments/stripe/.*`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/costexplorer.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/binded_support_plan`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/credits`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/credits_summary`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/credits/.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/contracts/\d+/download`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/users/contracts.*`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/users/contracts/\d+/actions/.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_config_id/\d+/users/discounts`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_configs`)},
	{Method: "ANY", PathReg: regexp.MustCompile(`/api/v1/unc/tenants/(?P<tenant_id>\d+)/notifications/.*`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/referral/code`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/referral/stats`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/promotion/code`)},
	{Method: "GET", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/promotion/stats`)},
	{Method: "POST", PathReg: regexp.MustCompile(`/api/v1/billing/tenants/(?P<tenant_id>\d+)/billing_action_check.*`)},
}

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 *usersvc.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 := usersvc.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
		req.Header.Set(billing2.AccountIDHeader, strconv.FormatUint(user.ID.Uint64(), 10))
		req.Header.Set(billing2.NeedPermissionCheckHeader, "true")
		req.Header.Set(billing2.RequestSource, billing2.RequestSourceConsole)
	}
	proxy := &httputil.ReverseProxy{Director: director}
	proxy.ServeHTTP(w, req)
}
