package compute_engine

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"

	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/db"
)

func (c *CMClient) DoHealthReport(ha HealthArgs) (*Report, error) {
	payload := map[string]any{
		"args": ha,
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s", c.path, healthPath), bytes.NewBuffer(data))
	if err != nil {
		log.Error("request failed", zap.Error(err))
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		log.Error("request cm failed", zap.Error(err))
		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != http.StatusOK {
		body := []byte{}
		_, err = resp.Body.Read(body)
		log.Error("health remote call failed", zap.String("error msg", string(body)), zap.Error(err), zap.Any("code", resp.StatusCode))
		return nil, fmt.Errorf("remote call failed")
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("read resp body failed", zap.Error(err))
		return nil, err
	}
	var report Report
	if err := json.Unmarshal(body, &report); err != nil {
		log.Error("unmarshal resp to report failed", zap.Error(err))
		return nil, err
	}
	return &report, nil
}

func (c *CMClient) FetchReport(reportUUID string) (*Report, error) {
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s/%s", c.path, fetchPath, reportUUID), nil)
	if err != nil {
		log.Error("request failed", zap.Error(err))
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		log.Error("request cm failed", zap.Error(err))
		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != http.StatusOK {
		body := []byte{}
		_, err = resp.Body.Read(body)
		log.Error("remote call failed", zap.String("error msg", string(body)), zap.Error(err), zap.Any("code", resp.StatusCode))
		return nil, err
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("read resp body failed", zap.Error(err))
		return nil, err
	}
	var report Report
	if err := json.Unmarshal(body, &report); err != nil {
		log.Error("unmarshal resp to report failed", zap.Error(err))
		return nil, err
	}
	return &report, nil
}

func (c *CMClient) DoComparisonReport(ca ComparisonReportArgs) (*ComparisonReport, error) {
	payload := map[string]any{
		"args": ca,
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s", c.path, comparisonPath), bytes.NewBuffer(data))
	if err != nil {
		log.Error("request failed", zap.Error(err))
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		log.Error("request cm failed", zap.Error(err))
		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != http.StatusOK {
		body := []byte{}
		_, err = resp.Body.Read(body)
		log.Error("health remote call failed", zap.String("error msg", string(body)), zap.Error(err), zap.Any("code", resp.StatusCode))
		return nil, fmt.Errorf("remote call failed")
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("read resp body failed", zap.Error(err))
		return nil, err
	}
	var report ComparisonReport
	if err := json.Unmarshal(body, &report); err != nil {
		log.Error("unmarshal resp to report failed", zap.Error(err))
		return nil, err
	}
	return &report, nil
}

func (c *CMClient) FetchComparisonReport(reportUUID string) (any, error) {
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s/%s", c.path, fetchPath, reportUUID), nil)
	if err != nil {
		log.Error("request failed", zap.Error(err))
		return nil, err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		log.Error("request cm failed", zap.Error(err))
		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != http.StatusOK {
		body := []byte{}
		_, err = resp.Body.Read(body)
		log.Error("remote call failed", zap.String("error msg", string(body)), zap.Error(err), zap.Any("code", resp.StatusCode))
		return nil, err
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("read resp body failed", zap.Error(err))
		return nil, err
	}
	var report any
	if err := json.Unmarshal(body, &report); err != nil {
		log.Error("unmarshal resp to report failed", zap.Error(err))
		return nil, err
	}
	return &report, nil
}

func (c *CMClient) TouchReport(r *db.Report, orgType string) error {
	CMArg := map[string]any{
		"cluster": r.ClusterID,
		"org_id":  strconv.FormatUint(uint64(r.OrgID), 10),
	}
	var args map[string]any
	err := json.Unmarshal([]byte(r.Arg), &args)
	if err != nil {
		log.Error("unmarshal args form db failed", zap.Error(err))
		return err
	}

	CMArg["start_ts"] = args["begin_ts"]
	CMArg["stop_ts"] = args["end_ts"]
	CMArg["org_type"] = orgType
	for k, v := range args {
		CMArg[k] = v
	}

	var path string
	switch r.ReportType {
	case "health2":
		path = "/tasks/health-report-v2/async"
	case "workload":
		path = "/tasks/healthcheck-report/async"
	case "infra":
		path = "/tasks/infra_report/async"
	case "slowlog-visual":
		path = "/tasks/slowlog-visual/async"
	}

	payload := map[string]any{
		"clinic_report_uuid": r.UUID,
		"args":               CMArg,
		"hook":               fmt.Sprintf("%s/clinic/api/v1/reports/%d/%s/%s", c.clinicHost, r.OrgID, r.ClusterID, r.UUID),
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	log.Info("touch compute-engine request: " + string(data))
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", c.path, path), bytes.NewBuffer(data))
	if err != nil {
		log.Error("request failed", zap.Error(err))
		return err
	}
	resp, err := c.client.Do(req)
	if err != nil {
		log.Error("request cm failed", zap.Error(err))
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != http.StatusOK {
		body := []byte{}
		_, err = resp.Body.Read(body)
		log.Error("report remote call failed", zap.String("error msg", string(body)), zap.Error(err), zap.Any("code", resp.StatusCode))
		return fmt.Errorf("remote call failed")
	}

	// logging info of compute-engine task to debug
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("read resp body failed", zap.Error(err))
	}
	log.Info("touch compute-engine result: " + string(body))
	return nil
}
