package regionalapi

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"path"
	"strings"
	"time"

	"github.com/gin-gonic/gin/binding"
	"github.com/pingcap/errors"
)

const dmProxyClusterPrefix = "/proxy/cluster/%s/dm/%s"

const (
	BackendOperatorV2 = "operator-v2"
	BackendLegacy     = "legacy"
)

// IsOperatorV2Backend reports whether DM OpenAPI should use dataplane service-proxy.
func IsOperatorV2Backend(backend string) bool {
	switch strings.ToLower(strings.TrimSpace(backend)) {
	case BackendOperatorV2, "operator_v2":
		return true
	default:
		return false
	}
}

// BuildDMApiURL returns the full service-proxy URL for DM master OpenAPI.
func BuildDMApiURL(gatewayBaseURL, sharedPoolID, dmClusterID, apiPath string) string {
	return strings.TrimRight(gatewayBaseURL, "/") + BuildDMApiPath(sharedPoolID, dmClusterID, apiPath)
}

// BuildDMApiPath returns the service-proxy path for DM master OpenAPI.
// apiPath is the DM master relative path, e.g. "/api/v1/tasks".
func BuildDMApiPath(sharedPoolID, dmClusterID, apiPath string) string {
	pathPart, queryPart, hasQuery := strings.Cut(apiPath, "?")
	if pathPart == "" {
		pathPart = "/"
	}
	joinedPath := path.Join(fmt.Sprintf(dmProxyClusterPrefix, sharedPoolID, dmClusterID), pathPart)
	if hasQuery {
		return joinedPath + "?" + queryPart
	}
	return joinedPath
}

// DMOpenAPIClient calls DM master OpenAPI through dataplane service-proxy.
type DMOpenAPIClient interface {
	Do(ctx context.Context, method, apiPath string, body []byte) (status int, respBody []byte, err error)
}

// DMConfig holds routing and auth settings for service-proxy DM access.
type DMConfig struct {
	GatewayBaseURL string
	AuthToken      string
	SharedPoolID   string
	DMClusterID    string
}

type dmOpenAPIClient struct {
	cfg        DMConfig
	httpClient *http.Client
}

// NewDMOpenAPIClient builds a DM OpenAPI client for operator-v2 backends.
func NewDMOpenAPIClient(cfg DMConfig) (DMOpenAPIClient, error) {
	if cfg.GatewayBaseURL == "" {
		return nil, errors.New("gateway base url is required for dm service-proxy client")
	}
	if cfg.AuthToken == "" {
		return nil, errors.New("auth token is required for dm service-proxy client")
	}
	if cfg.SharedPoolID == "" {
		return nil, errors.New("shared pool id is required for dm service-proxy client")
	}
	if cfg.DMClusterID == "" {
		return nil, errors.New("dm cluster id is required for dm service-proxy client")
	}

	return &dmOpenAPIClient{
		cfg: cfg,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // gateway uses internal certs
			},
		},
	}, nil
}

func (c *dmOpenAPIClient) Do(
	ctx context.Context, method, apiPath string, body []byte,
) (int, []byte, error) {
	url := BuildDMApiURL(c.cfg.GatewayBaseURL, c.cfg.SharedPoolID, c.cfg.DMClusterID, apiPath)

	var bodyReader io.Reader
	if len(body) > 0 {
		bodyReader = bytes.NewReader(body)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
	if err != nil {
		return 0, nil, errors.Trace(err)
	}
	req.Header.Set("Content-Type", binding.MIMEJSON)
	req.Header.Set("Authorization", "Bearer "+c.cfg.AuthToken)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return 0, nil, errors.Trace(err)
	}
	defer resp.Body.Close() //nolint:errcheck

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, errors.Trace(err)
	}
	return resp.StatusCode, respBody, nil
}
