package centralhandler

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/utils/auth0"
	"github.com/spf13/viper"
)

type CentralHandler interface {
	GetAllTenants() ([]forms.Tenant, error)
	GetTenant(tenantID uint) (*forms.Tenant, error)
	DownloadFile(path string) ([]byte, error)
}

var globalCentralHandler CentralHandler = &CentralHandlerImpl{}

func GetCentralHandler() CentralHandler {
	return globalCentralHandler
}

// test use only.
func SetCentralHandler(handler CentralHandler) {
	globalCentralHandler = handler
}

type CentralHandlerImpl struct {
}

func centralEnabled() bool {
	return viper.GetBool(config.DBaaSCentralEnable)
}

func signCentralAuth(req *http.Request) error {
	auth0Domain, auth0ClientID, auth0ClientSecret, auth0Audience := viper.GetString(config.DBaaSCentralAuth0Domain), viper.GetString(config.DBaaSCentralAuth0ClientID), viper.GetString(config.DBaaSCentralAuth0ClientSecret), viper.GetString(config.DBaaSCentralAuth0Audience)
	token, err := auth0.GetClientToken(auth0Domain, auth0ClientID, auth0ClientSecret, auth0Audience)
	if err != nil {
		return billing_errors.NewSystemError(err)
	}
	req.Header.Add("Authorization", fmt.Sprintf("bearer %s", token))
	return nil
}

var globalClient http.Client = http.Client{
	Timeout: 60 * time.Second,
}

func doRequestRaw(apiPath string, query *url.Values) ([]byte, error) {
	url := &url.URL{}
	url.Scheme = viper.GetString(config.DBaaSCentralScheme)
	url.Host = viper.GetString(config.DBaaSCentralHost)
	url.Path = fmt.Sprintf("/api/v1/admin/%s", strings.TrimLeft(apiPath, "/"))
	url.RawQuery = query.Encode()
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	err = signCentralAuth(req)
	if err != nil {
		return nil, err
	}
	resp, err := globalClient.Do(req)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	resp.Body.Close() // nolint: errcheck

	if resp.StatusCode != 200 {
		return nil, billing_errors.NewSystemError(fmt.Errorf("call central service resp code err %d, resp: %s", resp.StatusCode, string(bodyBytes)))
	}
	return bodyBytes, nil
}

func doRequest(apiPath string, query *url.Values, output interface{}) error {
	bodyBytes, err := doRequestRaw(apiPath, query)
	if err != nil {
		return err
	}
	if output != nil {
		err = json.Unmarshal(bodyBytes, output)
		if err != nil {
			return billing_errors.NewSystemError(err)
		}
	}
	return nil
}

type Tenant struct {
	ID                        uint64  `json:"id,string"`
	Name                      string  `json:"name"`
	Alias                     string  `json:"alias"`
	Timezone                  int32   `json:"timezone"`
	Email                     string  `json:"email"`
	Status                    string  `json:"status"`
	Plan                      string  `json:"plan"`
	Discount                  float64 `json:"discount"`
	DiscountDescription       string  `json:"discount_description"`
	BindCreditAt              uint64  `json:"bind_credit_at"`
	Country                   string  `json:"country"`
	HasDiscount               bool    `json:"has_discount"`
	DiscountStartTime         int64   `json:"discount_start_time"`
	DiscountEndTime           int64   `json:"discount_end_time"`
	CreatedAt                 int64   `json:"created_at"`
	Kind                      string  `json:"kind"`
	AccountManager            string  `json:"account_manager"`
	ThirdPartyAccountProvider string  `json:"third_party_account_provider"`
	ThirdPartyAccountCode     string  `json:"third_party_account_code"`
}

type ListTenantResp struct {
	Total int64    `json:"total"`
	Items []Tenant `json:"items"`
}

func (*CentralHandlerImpl) GetAllTenants() ([]forms.Tenant, error) {
	if !centralEnabled() {
		return make([]forms.Tenant, 0), nil
	}
	ret := &ListTenantResp{}
	query := url.Values{}
	query.Add("no-limit", "true")
	query.Add("brief", "true")
	err := doRequest("/orgs", &query, ret)
	if err != nil {
		return nil, err
	}
	output := make([]forms.Tenant, len(ret.Items))
	for i, item := range ret.Items {
		output[i] = forms.Tenant{
			TenantID:                  uint(item.ID),
			Name:                      item.Name,
			Email:                     item.Email,
			Plan:                      item.Plan,
			ThirdPartyAccountProvider: item.ThirdPartyAccountProvider,
			ThirdPartyAccountCode:     item.ThirdPartyAccountCode,
		}
	}
	return output, nil
}

func (*CentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	if !centralEnabled() {
		return &forms.Tenant{
			TenantID: tenantID,
		}, nil
	}
	ret := &ListTenantResp{}
	query := url.Values{}
	query.Add("no-limit", "true")
	query.Add("id", fmt.Sprint(tenantID))
	err := doRequest("/orgs", &query, ret)
	if err != nil {
		return nil, err
	}
	if ret.Total == 0 {
		return nil, billing_errors.NewObjectNotExistsError(fmt.Errorf("tenant %d not exits", tenantID))
	}
	if ret.Total > 1 {
		return nil, billing_errors.NewSystemError(errors.New("invalid get tenant result"))
	}
	item := ret.Items[0]
	output := &forms.Tenant{
		TenantID:                  uint(item.ID),
		Name:                      item.Name,
		Email:                     item.Email,
		Plan:                      item.Plan,
		ThirdPartyAccountProvider: item.ThirdPartyAccountProvider,
		ThirdPartyAccountCode:     item.ThirdPartyAccountCode,
	}
	return output, nil
}

func (*CentralHandlerImpl) DownloadFile(path string) ([]byte, error) {
	if !centralEnabled() {
		return []byte(""), nil
	}
	bodyBytes, err := doRequestRaw(fmt.Sprintf("/uploaded/%s", path), &url.Values{})
	if err != nil {
		return nil, err
	}
	return bodyBytes, nil
}
