//go:generate ${GOBIN}/mockgen -destination clinic_mock.go -package=clinic ${GO_MODULE}/pkg/clients/clinic Interface
package clinic

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

	eutil "github.com/tidbcloud/jiraport/pkg/errors"

	"gorm.io/gorm"
)

type Interface interface {
	GetTenantIDByTenantIDAndClusterID(tenantID string, clusterID string) (string, error)
	GetClinicOrgID(tenantID string) (string, error)
	GetClusterName(clusterID string) (string, error)
	GetClusterVersion(clusterID string) (string, error)
	GetDeployTypeByCluster(clusterID string) (string, error)
	GenerateReport(clinicOrgID, clusterID string) (string, error)

	GetRegionByTenantIDAndClusterID(tenantID string, clusterID string) (string, error)
	GetRegionContactByRegion(region string) (string, error)
	GetAliasByTenantIDAndClusterID(tenantID string, clusterID string) (string, error)
}

type ClinicClusterResp struct {
	Total uint                `json:"total"`
	Items []ClinicClusterItem `json:"items"`
}

type ClinicClusterItem struct {
	TenantID            string `json:"tenantID"`
	ClusterName         string `json:"clusterName"`
	Version             string `json:"clusterVersion"`
	ClusterDeployTypeV2 string `json:"clusterDeployTypeV2"`
}

type ClinicCli struct {
	client     *http.Client
	infoDB     *gorm.DB
	policyDB   *gorm.DB
	clinicAddr string
	authString string
}

// create clinic client.
func New(
	infoDB *gorm.DB,
	policyDB *gorm.DB,
	addr string,
	auth string,
) (*ClinicCli, error) {
	httpCli := &http.Client{Timeout: 5 * time.Second}
	return &ClinicCli{
		client:     httpCli,
		infoDB:     infoDB,
		policyDB:   policyDB,
		clinicAddr: addr,
		authString: auth,
	}, nil
}

func (cc *ClinicCli) GetTenantIDByTenantIDAndClusterID(tenantID string, clusterID string) (string, error) {
	// search by tenant
	if tenantID == `` || tenantID == `<no value>` {
		if clusterID != `` {
			clusterResp, err := cc.getClusterByID(clusterID)
			if err != nil {
				return "", err
			}
			if len(clusterResp.Items) > 0 {
				tenantID = clusterResp.Items[0].TenantID
			}

		}
	}
	return tenantID, nil
}

func (cc *ClinicCli) GetClusterName(clusterID string) (string, error) {
	var clusterName string
	if clusterID != `` {
		clusterResp, err := cc.getClusterByID(clusterID)
		if err != nil {
			return "", err
		}
		if len(clusterResp.Items) > 0 {
			clusterName = clusterResp.Items[0].ClusterName
		}

	}
	return clusterName, nil
}

func (cc *ClinicCli) GetDeployTypeByCluster(clusterID string) (string, error) {
	if clusterID == "" {
		return "", nil
	}
	clusterResp, err := cc.getClusterByID(clusterID)
	if err != nil {
		return "", err
	}
	if len(clusterResp.Items) > 0 {
		raw := clusterResp.Items[0].ClusterDeployTypeV2
		if raw != "" {
			// Take first word, capitalize: "premium resource pool" → "Premium"
			word := strings.SplitN(raw, " ", 2)[0]
			return strings.ToUpper(word[:1]) + strings.ToLower(word[1:]), nil
		}
	}
	return "", nil
}

func (cc *ClinicCli) GetClusterVersion(clusterID string) (string, error) {
	clusterResp, err := cc.getClusterByID(clusterID)
	if err != nil {
		return "", err
	}
	if len(clusterResp.Items) > 0 {
		return clusterResp.Items[0].Version, nil
	}
	return "", nil
}

func (cc *ClinicCli) GetRegionByTenantIDAndClusterID(tenantID string, clusterID string) (string, error) {
	if cc.infoDB == nil {
		return "", fmt.Errorf("infoDB is empty")
	}
	tenant, err := cc.GetTenantIDByTenantIDAndClusterID(tenantID, clusterID)
	if err != nil {
		return "", err
	}
	tenantInt, err := strconv.ParseUint(tenant, 10, 64)
	if err != nil {
		return ``, err
	}

	const SelectRegionByTenantID = `
	SELECT 
    CASE
        WHEN sales_force_region = '' THEN country_region
        ELSE sales_force_region
    END AS region
	FROM devops_prod.tenants where id = ?;`

	var region string
	res := cc.infoDB.Raw(SelectRegionByTenantID, tenantInt).Scan(&region)
	return region, eutil.CheckGORMErr(res)
}

func (cc *ClinicCli) GetRegionContactByRegion(region string) (string, error) {
	if cc.policyDB == nil {
		return "", fmt.Errorf("policyDB is empty")
	}

	const GetRegionContactByRegion = `
	select email from olympus.region_contacts where region = ?`

	var email string
	res := cc.policyDB.Raw(GetRegionContactByRegion, region).Scan(&email)
	return email, eutil.CheckGORMErr(res)
}

func (cc *ClinicCli) GetAliasByTenantIDAndClusterID(tenantID string, clusterID string) (string, error) {
	if cc.infoDB == nil {
		return "", fmt.Errorf("infoDB is empty")
	}

	tenant, err := cc.GetTenantIDByTenantIDAndClusterID(tenantID, clusterID)
	if err != nil {
		return "", err
	}
	tenantInt, err := strconv.ParseUint(tenant, 10, 64)
	if err != nil {
		return ``, err
	}

	const GetAliasByTenant = `
	select name from devops_prod.tenants where id = ?`

	var alias string
	res := cc.infoDB.Raw(GetAliasByTenant, tenantInt).Scan(&alias)
	return alias, eutil.CheckGORMErr(res)
}

func (cc *ClinicCli) getClusterByID(clusterID string) (*ClinicClusterResp, error) {
	req, _ := http.NewRequest("GET",
		fmt.Sprintf("%s/clinic/api/v1/dashboard/clusters2?cluster_id=%s", cc.clinicAddr, clusterID),
		nil)
	req.Header.Add("Authorization", "Bearer "+cc.authString)

	res, err := cc.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fail to get cluster from clinic, %w", err)
	}
	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("fail to read response body, %w", err)
	}

	var structRes ClinicClusterResp
	err = json.Unmarshal(resBody, &structRes)
	if err != nil {
		return nil, fmt.Errorf("fail to unmarshal response body, %w", err)
	}
	return &structRes, nil
}

type ExternalResponse struct {
	ID string `json:"id"`
}

func (cc *ClinicCli) GetClinicOrgID(tenantID string) (string, error) {
	req, _ := http.NewRequest("GET",
		fmt.Sprintf("%s/clinic/api/v1/orgs/external/%s", cc.clinicAddr, tenantID),
		nil)
	req.Header.Add("Authorization", "Bearer "+cc.authString)

	res, err := cc.client.Do(req)
	if err != nil {
		return "", fmt.Errorf("fail to get clinic org id, %w", err)
	}
	defer res.Body.Close()

	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		return "", fmt.Errorf("fail to read response body, %w", err)
	}

	var structResp ExternalResponse
	err = json.Unmarshal(resBody, &structResp)
	if err != nil {
		return "", fmt.Errorf("fail to unmarshal response body, %w", err)
	}
	return structResp.ID, nil
}

type GenerateReportResponse struct {
	ReportID string `json:"report_id"`
}

func (cc *ClinicCli) GenerateReport(clinicOrgID, clusterID string) (string, error) {
	params := url.Values{}
	params.Add("report_type", "workload") // workload means system check.

	// ts: from now to 6 hours before.
	endTS := time.Now().Unix()
	beginTS := endTS - 21600
	body := strings.NewReader("{\"begin_ts\": " + fmt.Sprint(beginTS) + ", " + "\"end_ts\": " + fmt.Sprint(endTS) + ", \"package_id\": \"string\"}")

	req, _ := http.NewRequest("POST",
		fmt.Sprintf("%s/clinic/api/v1/reports/%s/%s?%s", cc.clinicAddr, clinicOrgID, clusterID, params.Encode()),
		body)
	req.Header.Add("Authorization", "Bearer "+cc.authString)

	res, err := cc.client.Do(req)
	if err != nil {
		return "", fmt.Errorf("fail to generate report, %w", err)
	}
	defer res.Body.Close()
	resBody, err := io.ReadAll(res.Body)
	if err != nil {
		return "", fmt.Errorf("fail to read response body, %w", err)
	}

	var structResp GenerateReportResponse
	err = json.Unmarshal(resBody, &structResp)
	if err != nil {
		return "", fmt.Errorf("fail to unmarshal response body, %w", err)
	}
	return structResp.ReportID, nil
}
