package cliniccli

import (
	"encoding/json"
	"fmt"
	"io"
	"jirasop/model"
	"jirasop/pkg/infra/config"
	"jirasop/pkg/infra/log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/go-logr/logr"
)

type ClinicClient struct {
	client *http.Client
	logger logr.Logger
	config config.ClinicConfig
}

func NewClinicClient(logconfig config.LogConfig, clinicconf config.ClinicConfig) *ClinicClient {
	logger := log.Logger.WithName("ClinicClient")
	httpCli := &http.Client{Timeout: 600 * time.Second}
	return &ClinicClient{logger: logger, client: httpCli, config: clinicconf}
}

// Get the Auth0 Token.
func (cc *ClinicClient) GetToken() (string, error) {
	postData := url.Values{}
	postData.Add("grant_type", cc.config.Auth0GrantType)
	postData.Add("client_id", cc.config.Auth0ClientID)
	postData.Add("client_secret", cc.config.Auth0ClientSecret)
	postData.Add("audience", cc.config.Auth0Audience)

	req, _ := http.NewRequest("POST", cc.config.Auth0URL, strings.NewReader(postData.Encode()))
	req.Header.Set("User-Agent", "curl")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Cookie", cc.config.Auth0Cookie)

	var structRes model.AuthResponse
	res, err := cc.client.Do(req)
	if err != nil {
		cc.logger.Error(err, "fail to get auth 0")
		return "", err
	}

	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return "", err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return "", err
	}

	return structRes.Access_token, nil
}

func (cc *ClinicClient) GetOrgIdByTenant(tenant_id string) (string, error) {
	externalURL := cc.config.ClinicAddr + "clinic/api/v1/orgs/external"
	url := externalURL + "/" + tenant_id
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+cc.config.Token)

	var structRes model.ExternalResponse
	res, err := cc.client.Do(req)
	if err != nil {
		cc.logger.Error(err, "fail to get org id")
		return "", err
	}

	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return "", err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return "", err
	}

	return structRes.Id, nil
}

// get Region information via Clinic API.
func (cc *ClinicClient) GetRegionInfo(tenant_id string, cluster_id string) (string, error) {
	url := cc.config.ClinicAddr + "clinic/api/v1/orgs/" + tenant_id + "/clusters/" + cluster_id
	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer "+cc.config.Token)

	var structRes model.ClinicResponse
	res, err := cc.client.Do(req)
	if err != nil {
		cc.logger.Error(err, "fail to get region info")
		return "", err
	}
	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return "", err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return "", err
	}

	return structRes.Region, nil
}

// get cluster unique prom ID.
func (cc *ClinicClient) GetClusterId(server_domain string, region string, biz_type string, application_id string, token string, vendor string) (string, error) {
	url := cc.config.PromAddr + "/?vendor=" + vendor + "&region=" + region + "&bizType=" + biz_type + "&applicationID=" + application_id
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+token)

	var structRes model.ClusterResponse

	res, err := cc.client.Do(req)
	if err != nil {
		cc.logger.Error(err, "fail to get prom id")
		return "", err
	}

	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return "", err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return "", err
	}

	// if there is no cluster, it means that this cluster is dev tier.
	if len(structRes.Data.Clusters) == 0 {
		return "", nil
	}
	return structRes.Data.Clusters[0].Id, nil

}

func GetPromURL(serverDomain string, clusterPromID string) string {
	return "https://" + serverDomain + "/internal/metrics/" + clusterPromID
}

func toPromTIme(origin string) string {
	split_slice := strings.Split(origin, " ")
	return split_slice[0] + "T" + split_slice[1] + ".000Z"
}

func (cc *ClinicClient) GetOneMetricRequest(metric string,
	clusterID string,
	tenantID string,
	start int64,
	end int64,
	step string) (*model.ThanosData, error) {
	token_auth0, err := cc.GetToken()
	if err != nil {
		cc.logger.Error(err, "fail to get token")
		return nil, err
	}
	orgID, err := cc.GetOrgIdByTenant(tenantID)
	if err != nil {
		cc.logger.Error(err, "fail to get org id")
		return nil, err
	}

	region, err := cc.GetRegionInfo(orgID, clusterID)
	if err != nil {
		cc.logger.Error(err, "fail to get region")
		return nil, err
	}

	serverDomain := cc.config.Domain[region]
	if serverDomain == "" {
		err = fmt.Errorf("cannot find domain info in list")
		cc.logger.Error(err, "domain not found")
		return nil, err
	}

	vendor := cc.config.Vendor[region]
	if vendor == "" {
		err = fmt.Errorf("cannot find vendor info in list")
		cc.logger.Error(err, "vendor not found")
		return nil, err
	}

	id, err := cc.GetClusterId(serverDomain, region, "tidbcloud", clusterID, token_auth0, vendor)
	if err != nil {
		cc.logger.Error(err, "fail to get cluster prom id")
		return nil, err
	}

	promURL := GetPromURL(serverDomain, id)

	params := url.Values{}
	params.Add(`query`, metric)
	params.Add(`start`, toPromTIme(time.Unix(start, 0).UTC().Format("2006-01-02 15:04:05")))
	params.Add(`end`, toPromTIme(time.Unix(end, 0).UTC().Format("2006-01-02 15:04:05")))
	params.Add(`step`, step)

	req, _ := http.NewRequest("GET", promURL+`/api/v1/query_range?`+params.Encode(), nil)

	var structRes model.ThanosResponse
	res, err := cc.client.Do(req)

	if err != nil {
		cc.logger.Error(err, "fail to get prom id")
		return nil, err
	}
	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return nil, err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return nil, err
	}

	return &structRes.Data, nil
}

func (cc *ClinicClient) GetActivities(from int, to int, clusterID string) ([]*model.Activity, error) {
	params := url.Values{}
	params.Add(`source`, cc.config.EventSource)
	req, _ := http.NewRequest("GET", cc.config.EventURL+clusterID+`/from/`+strconv.Itoa(from)+
		`/to/`+strconv.Itoa(to)+`?`+params.Encode(), nil)
	req.Header.Add("x-activityhub-source", cc.config.EventSource)
	req.Header.Add("x-activityhub-key", cc.config.EventKey)

	var structRes model.ActivityResp

	res, err := cc.client.Do(req)
	if err != nil {
		cc.logger.Error(err, "fail to get activities", "cluster id", clusterID)
		return nil, err
	}

	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		cc.logger.Error(err, "fail to read body")
		return nil, err
	}

	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		cc.logger.Error(err, "fail to unmarshal result")
		return nil, err
	}

	return structRes.Result, nil
}
