package tidbworker

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"net/http"

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

var _ Api = &api{}

const (
	cleanTiDBWorkerPath  = "/scaler/api/v1/admin/clean?keyspace=%s"
	pauseTiDBWorkerPath  = "/scaler/api/v1/admin/pause?keyspace=%s"
	resumeTiDBWorkerPath = "/scaler/api/v1/admin/resume?keyspace=%s"
)

type Api interface {
	Clean(ctx context.Context, keyspaceName string) error
	Pause(ctx context.Context, keyspaceName string) error
	Resume(ctx context.Context, keyspaceName string) error
}

type api struct {
	client      *http.Client
	serviceAddr string
}

func NewApi(serviceAddr string, tlsConfig *tls.Config) Api {
	var (
		scheme = "http://"
		client = http.DefaultClient
	)
	if tlsConfig != nil {
		scheme = "https://"
		client.Transport = &http.Transport{
			TLSClientConfig:   tlsConfig,
			ForceAttemptHTTP2: true,
		}
	}

	return &api{
		client:      client,
		serviceAddr: scheme + serviceAddr,
	}
}

func (a *api) newRequest(
	ctx context.Context,
	method string,
	path string,
	body io.Reader,
	headers ...[]string,
) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, method, a.serviceAddr+path, body)
	if err != nil {
		return nil, fmt.Errorf("create http request error: %w", err)
	}
	for _, header := range headers {
		req.Header.Set(header[0], header[1])
	}

	// Try to get logger from context
	logger, err := logr.FromContext(ctx)
	if err == nil {
		logger.V(1).Info("create http request", "url", req.URL.String())
	}
	return req, nil
}

func (a *api) do(req *http.Request, statusChecker func(statusCode int, body []byte) error) ([]byte, error) {
	resp, err := a.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http request error: %w", err)
	}
	defer resp.Body.Close() //nolint:errcheck // ignore error
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read body error: %w", err)
	}
	err = statusChecker(resp.StatusCode, body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

func (a *api) Clean(ctx context.Context, keyspaceName string) error {
	return a.callKeyspaceAdmin(ctx, "clean", cleanTiDBWorkerPath, keyspaceName)
}

func (a *api) Pause(ctx context.Context, keyspaceName string) error {
	return a.callKeyspaceAdmin(ctx, "pause", pauseTiDBWorkerPath, keyspaceName)
}

func (a *api) Resume(ctx context.Context, keyspaceName string) error {
	return a.callKeyspaceAdmin(ctx, "resume", resumeTiDBWorkerPath, keyspaceName)
}

func (a *api) callKeyspaceAdmin(ctx context.Context, op, pathFmt, keyspaceName string) error {
	req, err := a.newRequest(ctx, http.MethodGet, fmt.Sprintf(pathFmt, keyspaceName), nil)
	if err != nil {
		return err
	}

	_, err = a.do(req, func(statusCode int, body []byte) error {
		if statusCode == http.StatusOK {
			return nil
		}
		return fmt.Errorf(
			"%s tidb worker failed, keyspace: %s, status: %s, body: %s",
			op,
			keyspaceName,
			http.StatusText(statusCode),
			body,
		)
	})
	return err
}
