package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/pingcap/errors"
	"github.com/pingcap/tiflow/dm/openapi"
	"github.com/tidbcloud/dataflow-service/internal/constants"
	"go.uber.org/zap"
)

type DMOpenapiClient struct {
	AgentClient
	dmAddr    string
	clusterID uint64
}

var ErrorDMResourceNotFound = errors.New("dm resource not found")

func openapiErrorUnwrapper(logger *zap.Logger, bodyBytes []byte) error {
	if len(bodyBytes) == 0 {
		return errors.New("dm openapi response empty")
	} else if strings.Contains(string(bodyBytes), "not exist") || strings.Contains(string(bodyBytes), "response_status:404") {
		logger.Error("dm openapi response 404", zap.String("error", string(bodyBytes)))
		return ErrorDMResourceNotFound
	}
	return errors.New(string(bodyBytes))
}

func (c *DMOpenapiClient) requestOpenapi(ctx context.Context, method, path string, body any) ([]byte, error) {

	// fmt.Println("requestOpenapi URL:", method, path)
	// var bd io.Reader
	// if b, ok := body.(io.Reader); ok {
	// 	bd = b
	// } else {
	// 	bs, err := json.Marshal(body)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// 	fmt.Println("requestOpenapi Request:", string(bs))
	// 	bd = bytes.NewReader(bs)
	// }
	// req, _ := http.NewRequestWithContext(ctx, method, c.dmAddr+path, bd)
	// req.Header.Set("content-type", "application/json")
	// resp, err := http.DefaultClient.Do(req)
	// if err != nil {
	// 	return nil, err
	// }
	// defer resp.Body.Close()
	// data, err := io.ReadAll(resp.Body)
	// if err != nil {
	// 	return nil, fmt.Errorf("dmopenapi http request read body failed, status:%d, err:%v", resp.StatusCode, err)
	// }
	// if resp.StatusCode >= http.StatusMultipleChoices {
	// 	return nil, fmt.Errorf("dmopenapi http request read body failed, status:%d, body:%s", resp.StatusCode, string(data))
	// }
	// fmt.Println("requestOpenapi Response:", string(data))
	// return data, nil

	path = filepath.Join("proxy/dataflow-agent/proxy/dm", path)
	extraHeaders := map[string]string{
		constants.DMAddrHeader:    c.dmAddr,
		constants.ClusterIDHeader: strconv.FormatUint(c.clusterID, 10),
		"content-type":            "application/json",
	}
	return c.FetchResponseWithHeader(ctx, method, path, body, extraHeaders, openapiErrorUnwrapper)
}

func (c *DMOpenapiClient) CreateDataSource(ctx context.Context, req *openapi.CreateSourceRequest) (*openapi.Source, error) {
	respBytes, err := c.requestOpenapi(ctx, http.MethodPost, "/api/v1/sources", req)
	if err != nil {
		return nil, err
	}

	var resp openapi.Source
	if err = json.Unmarshal(respBytes, &resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}

func (c *DMOpenapiClient) GetDataSource(ctx context.Context, name string) (*openapi.Source, error) {
	respBytes, err := c.requestOpenapi(ctx, http.MethodGet, "/api/v1/sources/"+name+"?with_status=true", nil)
	if err != nil {
		return nil, err
	}

	var resp openapi.Source
	if err = json.Unmarshal(respBytes, &resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}

func (c *DMOpenapiClient) DeleteDataSource(ctx context.Context, name string) error {
	_, err := c.requestOpenapi(ctx, http.MethodDelete, "/api/v1/sources/"+name, nil)
	return err
}

func (c *DMOpenapiClient) CreateTask(ctx context.Context, req *openapi.CreateTaskRequest) (*openapi.OperateTaskResponse, error) {
	respBytes, err := c.requestOpenapi(ctx, http.MethodPost, "/api/v1/tasks", req)
	if err != nil {
		return nil, err
	}

	var resp openapi.OperateTaskResponse
	if err = json.Unmarshal(respBytes, &resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}

func (c *DMOpenapiClient) DeleteTask(ctx context.Context, name string) error {
	_, err := c.requestOpenapi(ctx, http.MethodDelete, "/api/v1/tasks/"+name+"?force=true", nil)
	return err
}

func (c *DMOpenapiClient) StartTask(ctx context.Context, name string, req *openapi.StartTaskRequest) error {
	_, err := c.requestOpenapi(ctx, http.MethodPost, "/api/v1/tasks/"+name+"/start", req)
	return err
}

func (c *DMOpenapiClient) StopTask(ctx context.Context, name string, req *openapi.StopTaskRequest) error {
	_, err := c.requestOpenapi(ctx, http.MethodPost, "/api/v1/tasks/"+name+"/stop", req)
	return err
}

func (c *DMOpenapiClient) QueryTaskStatus(ctx context.Context, name string, sourceList openapi.SourceNameList) (*openapi.GetTaskStatusResponse, error) {
	sources := strings.Join(sourceList, ",")
	path := fmt.Sprintf("/api/v1/tasks/%s/status?source_name_list=%s", name, sources)
	respBytes, err := c.requestOpenapi(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, err
	}

	var resp openapi.GetTaskStatusResponse
	if err = json.Unmarshal(respBytes, &resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}

func (c *DMOpenapiClient) ListWorks(ctx context.Context) (*openapi.GetClusterWorkerListResponse, error) {
	respBytes, err := c.requestOpenapi(ctx, http.MethodGet, "/api/v1/cluster/workers", nil)
	if err != nil {
		return nil, err
	}

	var resp openapi.GetClusterWorkerListResponse
	if err = json.Unmarshal(respBytes, &resp); err != nil {
		return nil, errors.Trace(err)
	}
	return &resp, nil
}
