package dm

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

	"github.com/go-logr/logr"
	"github.com/tidbcloud/pkgv2/clusterproxy/serverless/dm"
)

const (
	listSourcesPath = "/api/v1/sources"
)

type Api interface {
	ListSources(ctx context.Context) (dm.GetSourceListResponse, 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) ListSources(ctx context.Context) (dm.GetSourceListResponse, error) {
	req, err := a.newRequest(ctx, http.MethodGet, listSourcesPath, nil,
		[]string{"Content-Type", "application/json"},
		[]string{"Accept", "application/json"})
	if err != nil {
		return dm.GetSourceListResponse{}, err
	}

	body, err := a.do(req, func(statusCode int, body []byte) error {
		if statusCode == http.StatusOK {
			return nil
		}
		return fmt.Errorf(
			"list sources failed, status: %s, body: %s",
			http.StatusText(statusCode),
			body,
		)
	})
	if err != nil {
		return dm.GetSourceListResponse{}, err
	}

	var resp dm.GetSourceListResponse
	if err := json.Unmarshal(body, &resp); err != nil {
		return dm.GetSourceListResponse{}, fmt.Errorf("unmarshal response body error: %w", err)
	}
	return resp, nil
}
