package billing

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

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/spf13/viper"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"go.uber.org/zap"
)

const (
	// DBaaSAccountScheme  is the rpc scheme
	DBaaSAccountScheme = "dbaas-account-scheme"
	// DBaaSAccountHost is the rpc call addr: host with port
	DBaaSAccountHost = "dbaas-account-host"
	// DBaaSAccountAuth0Domain  is the auth0 domain
	DBaaSAccountAuth0Domain = "dbaas-account-auth0-domain"
	// DBaaSAccountAuth0ClientID  is the auth0 client id
	DBaaSAccountAuth0ClientID = "dbaas-account-auth0-client-id"
	// DBaaSAccountAuth0ClientSecret is the auth0 client secret
	DBaaSAccountAuth0ClientSecret = "dbaas-account-auth0-client-secret" //nolint:gosec
	// DBaaSAccountAuth0Audience  is the auth0 client audience
	DBaaSAccountAuth0Audience = "dbaas-account-auth0-client-audience"
)

func (c Context) EnableOrgDevTier() error {
	// API: https://github.com/tidbcloud/account/blob/4a5b425f1be1402732613cca354a944476e225db/admin/docs/swagger.json#L503
	ret := &EnableOrgDevTierResp{}
	query := url.Values{}
	err := doAccountRequest("PUT", fmt.Sprintf("/orgs/%d/dev_tier", c.tenantAndUser.TenantID), &query, ret)
	if err != nil {
		c.Log().Error("failed to query trial record", zap.Error(err))
		return errcode.NewInternalErr(errors.New("failed to enable dev tier"))
	}
	return nil

	/*tx := c.GetDB()

	enabled, err := c.IsOrgDevTierEnabled()
	if err != nil {
		return err
	}

	if enabled {
		c.Log().Info("Dev Tier already enabled")
		return nil
	}

	trialCount, err := entity.NewTrialQuerySet(tx).TenantIDEq(c.tenantAndUser.TenantID).TrialTypeEq(entity.TrialTypeDevTier).Count()
	if err != nil {
		c.Log().Error("failed to query trial record", zap.Error(err))
		return errcode.NewInternalErr(errors.New("failed to enable dev tier"))
	}
	if trialCount != 0 {
		c.Log().Error("already enabled dev tier")
		return nil
	}
	now := time.Now()
	estimatedEndedAt := now.Add(config.Duration4DevTierTrialTotal)

	trialRecord := &entity.Trial{
		CreatedByID:      c.tenantAndUser.ID,
		TenantID:         c.tenantAndUser.TenantID,
		StartedAt:        now,
		EstimatedEndedAt: &estimatedEndedAt,
		TrialType:        entity.TrialTypeDevTier,
	}
	err = trialRecord.Create(tx)
	if err != nil {
		if gormutil.IsDuplicatedError(err) {
			c.Log().Info("trial record duplicated")
			return nil
		}
		c.Log().Error("failed to add trial record", zap.Error(err))
		return errcode.NewInternalErr(errors.New("failed to add trial record"))
	}

	t, err := c.GetTenantEntity()
	if err != nil {
		return err
	}

	if t.Plan == entity.TenantPlanNewUser {
		// change to free trial to avoid send new user notify email.
		t.Plan = entity.TenantPlanFreeTrial
		err = t.Update(tx, entity.TenantDBSchema.Plan)
		if err != nil {
			c.Log().Error("failed to update tenant plan to free trial", zap.Error(err))
			return err
		}
	}
	return nil*/
}

// for EnableOrgDevTier account API

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

type EnableOrgDevTierResp struct {
	// EmptyResponse
}

func signAccountAuth(req *http.Request) error {
	auth0Domain, auth0ClientID, auth0ClientSecret, auth0Audience := viper.GetString(DBaaSAccountAuth0Domain), viper.GetString(DBaaSAccountAuth0ClientID), viper.GetString(DBaaSAccountAuth0ClientSecret), viper.GetString(DBaaSAccountAuth0Audience)
	token, err := auth0.GetClientToken(auth0Domain, auth0ClientID, auth0ClientSecret, auth0Audience)
	if err != nil {
		return err
	}
	req.Header.Add("Authorization", fmt.Sprintf("bearer %s", token))
	return nil
}

func doAccountRequest(method, apiPath string, query *url.Values, output interface{}) error {
	url := &url.URL{}
	url.Scheme = viper.GetString(DBaaSAccountScheme)
	url.Host = viper.GetString(DBaaSAccountHost)
	url.Path = fmt.Sprintf("/api/v1/admin/%s", strings.TrimLeft(apiPath, "/"))
	url.RawQuery = query.Encode()
	req, err := http.NewRequest(method, url.String(), nil)
	if err != nil {
		return err
	}
	err = signAccountAuth(req)
	if err != nil {
		return err
	}
	resp, err := accountClient.Do(req)
	if err != nil {
		return err
	}
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	resp.Body.Close() // nolint: errcheck

	if resp.StatusCode != 200 {
		return fmt.Errorf("call account service resp code err %d, resp: %s", resp.StatusCode, string(bodyBytes))
	}
	if output != nil {
		err = json.Unmarshal(bodyBytes, output)
		if err != nil {
			return err
		}
	}
	return nil
}
