package clusteragent

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"github.com/go-resty/resty/v2"
	"github.com/pingcap/errors"

	"github.com/tidbcloud/ticdc-service-ng/pkg/util"
)

// Client provides access to cluster-agent APIs via the API Gateway.
type Client interface {
	// GetPublicKey retrieves the RSA public key from the cluster-agent.
	GetPublicKey(ctx context.Context) ([]byte, error)
	// QueryTiCDCTablesEligibility queries table eligibility for TiCDC replication,
	// returning both eligible and ineligible tables in a single call.
	QueryTiCDCTablesEligibility(ctx context.Context, clusterID string,
		encUser, encPwd []byte) (eligible, ineligible *TablesEligibilityResult, err error)
}

// TablesEligibilityResult holds schema/table pairs for one eligibility group.
type TablesEligibilityResult struct {
	Schemas []string
	Tables  []string
}

// queryTablesEligibilityReq is the request body for QueryTiCDCTablesEligibility.
// Uses camelCase JSON tags to match gRPC Gateway serialization.
type queryTablesEligibilityReq struct {
	EncryptedUsername []byte `json:"encryptedUsername"`
	EncryptedPassword []byte `json:"encryptedPassword"`
}

// tableSchemaInfo is the JSON shape for a single eligibility group in the response.
type tableSchemaInfo struct {
	TableSchemas []string `json:"tableSchemas"`
	TableNames   []string `json:"tableNames"`
}

// queryTablesEligibilityResp is the response body for QueryTiCDCTablesEligibility.
// Uses camelCase JSON tags to match gRPC Gateway serialization.
type queryTablesEligibilityResp struct {
	EligibleTables   tableSchemaInfo `json:"eligibleTables"`
	IneligibleTables tableSchemaInfo `json:"ineligibleTables"`
}

// getPublicKeyResp is the response body for GetPublicKey.
type getPublicKeyResp struct {
	Pem []byte `json:"pem"`
}

var _ Client = &clientImpl{}

type clientImpl struct {
	cli       *resty.Client
	namespace string
}

func (c *clientImpl) GetPublicKey(ctx context.Context) ([]byte, error) {
	ns := url.PathEscape(c.namespace)
	path := fmt.Sprintf("/cluster-agent/%s/api/v1/public_key", ns)
	resp, err := c.cli.R().SetContext(ctx).Get(path)
	if err != nil {
		return nil, fmt.Errorf("get public key request failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, &util.StatusError{
			StatusCode: resp.StatusCode(),
			Status:     resp.Status(),
			Body:       util.TruncateBody(resp.Body()),
		}
	}

	var result getPublicKeyResp
	if err := json.Unmarshal(resp.Body(), &result); err != nil {
		return nil, errors.Annotate(err, "unmarshal public key response failed")
	}
	return result.Pem, nil
}

func (c *clientImpl) QueryTiCDCTablesEligibility(ctx context.Context, clusterID string,
	encUser, encPwd []byte,
) (eligible, ineligible *TablesEligibilityResult, err error) {
	ns := url.PathEscape(c.namespace)
	clusterID = url.PathEscape(clusterID)
	path := fmt.Sprintf("/cluster-agent/%s/api/v1/clusters/%s:query_ticdc_tables_eligibility",
		ns, clusterID)
	body := queryTablesEligibilityReq{
		EncryptedUsername: encUser,
		EncryptedPassword: encPwd,
	}

	resp, err := c.cli.R().SetContext(ctx).SetBody(body).Post(path)
	if err != nil {
		return nil, nil, fmt.Errorf("query ticdc tables eligibility request failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, nil, &util.StatusError{
			StatusCode: resp.StatusCode(),
			Status:     resp.Status(),
			Body:       util.TruncateBody(resp.Body()),
		}
	}

	var result queryTablesEligibilityResp
	if err := json.Unmarshal(resp.Body(), &result); err != nil {
		return nil, nil, errors.Annotate(err, "unmarshal query tables eligibility response failed")
	}
	return &TablesEligibilityResult{
			Schemas: result.EligibleTables.TableSchemas,
			Tables:  result.EligibleTables.TableNames,
		}, &TablesEligibilityResult{
			Schemas: result.IneligibleTables.TableSchemas,
			Tables:  result.IneligibleTables.TableNames,
		}, nil
}
