package compute_engine

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httputil"
	"time"

	"github.com/pingcap/errors"

	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/ngm/performance_insight/compute_engine/index_advisor"
	"github.com/pingcap-inc/clinic/pkg/ngm/performance_insight/compute_engine/unstable_plan"
)

const (
	taskPathUnstablePlan = "/tasks/ce-job-unstable-plan/sync"
	taskPathIndexAdvisor = "/tasks/ce-job-index-advisor/sync"
)

type ComputerEngineClient struct {
	client *http.Client
	ceUrl  string
	env    string
}

func NewComputerEngineClient(ceUrl, env string) *ComputerEngineClient {
	client := &http.Client{}
	return &ComputerEngineClient{
		client: client,
		ceUrl:  ceUrl,
		env:    env,
	}
}

// CreateUnstablePlanTask creates a task and blocks until the task is finished.
func (c *ComputerEngineClient) CreateUnstablePlanTask(ctx context.Context, task *db.PerformanceInsightTask) ([]unstable_plan.Result, error) {
	args := unstable_plan.Args{
		Addr:                task.PerformanceInsightCluster.ConnectHost,
		Port:                task.PerformanceInsightCluster.ConnectPort,
		User:                task.PerformanceInsightCluster.ConnectAccountName,
		PasswdSecret:        task.PerformanceInsightCluster.ConnectPasswordSecretID,
		TaskCreateTimeEpoch: task.CreatedAt.Unix(),
		OffsetHours:         task.OffsetHours,
		OnlyValidateAccount: false,
	}
	return c.doUnstablePlanRequest(ctx, args)
}

func (c *ComputerEngineClient) CreateIndexAdvisorTask(ctx context.Context, task *db.PerformanceInsightTask) (index_advisor.ResultWrapper, error) {
	args := index_advisor.Args{
		Addr:                task.PerformanceInsightCluster.ConnectHost,
		Port:                task.PerformanceInsightCluster.ConnectPort,
		User:                task.PerformanceInsightCluster.ConnectAccountName,
		PasswdSecret:        task.PerformanceInsightCluster.ConnectPasswordSecretID,
		TaskCreateTimeEpoch: task.CreatedAt.Unix(),
		OffsetHours:         task.OffsetHours,

		ClusterInfo: index_advisor.ClusterInfo{
			Env:        c.env,
			DeployType: task.PerformanceInsightCluster.Cluster.DeployType,
			Provider:   task.PerformanceInsightCluster.Cluster.Provider,
			Region:     task.PerformanceInsightCluster.Cluster.Region,
			TenantID:   fmt.Sprintf("%d", task.PerformanceInsightCluster.Cluster.TenantID),
			ProjectID:  task.PerformanceInsightCluster.Cluster.ProjectID,
			ClusterID:  task.PerformanceInsightCluster.Cluster.ClusterID,
		},
	}
	return c.doIndexAdvisorRequest(ctx, args)
}

func (c *ComputerEngineClient) ValidateTask(ctx context.Context, addr, user, passwdSecret string, port uint) error {
	// Build request body.
	args := unstable_plan.Args{
		Addr:                addr,
		Port:                port,
		User:                user,
		PasswdSecret:        passwdSecret,
		TaskCreateTimeEpoch: time.Now().Unix(),
		OffsetHours:         0,
		OnlyValidateAccount: true,
	}
	res, err := c.doUnstablePlanRequest(ctx, args)
	if err != nil {
		return err
	}
	if len(res) != 1 || res[0].Digest == "" {
		return errors.New("validate mysql account failed")
	}
	return nil
}

func (c *ComputerEngineClient) doUnstablePlanRequest(ctx context.Context, args unstable_plan.Args) ([]unstable_plan.Result, error) {
	// Build request body.
	body := unstable_plan.Reqeust{Args: args}
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	// Do request.
	respBytes, err := c.doRequest(ctx, taskPathUnstablePlan, bodyJson)
	if err != nil {
		return nil, err
	}
	// Unmarshal response body.
	var resp unstable_plan.Response
	if err := json.Unmarshal(respBytes, &resp); err != nil {
		return nil, err
	}
	return resp.Data.Results, nil
}

func (c *ComputerEngineClient) doIndexAdvisorRequest(ctx context.Context, args index_advisor.Args) (index_advisor.ResultWrapper, error) {
	// Build request body.
	body := index_advisor.Reqeust{Args: args}
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return index_advisor.ResultWrapper{}, err
	}
	// Do request.
	respBytes, err := c.doRequest(ctx, taskPathIndexAdvisor, bodyJson)
	if err != nil {
		return index_advisor.ResultWrapper{}, err
	}
	// Unmarshal response body.
	var resp index_advisor.Response
	if err := json.Unmarshal(respBytes, &resp); err != nil {
		return index_advisor.ResultWrapper{}, err
	}
	return resp.Data, nil
}

func (c *ComputerEngineClient) doRequest(ctx context.Context, taskPath string, body []byte) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.ceUrl, taskPath), bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyRaw, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		drep, _ := httputil.DumpRequest(req, true)
		dresp, _ := httputil.DumpResponse(resp, false)
		return nil, errors.Errorf("failed to do http to ce unstable plan task, request: %s, response: %s, responseBody: %s",
			string(drep), string(dresp), string(respBodyRaw))
	}
	return respBodyRaw, nil
}
