package facade

import (
	"context"
	"fmt"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/clusterproxy"
	"github.com/tidbcloud/pkgv2/clusterproxy/serverless"
	"github.com/tidbcloud/pkgv2/clusterproxy/serverless/dm"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

// cluster proxy
type ClusterProxyProviderFacade interface {
	GetClusterProxy(
		ctx context.Context,
		cluster *tidbmgmtservicev1.Cluster,
		operator *clusterproxy.ClusterOperator,
	) (ClusterProxyFacade, bizerr.BizError)
	GetBranchProxy(
		ctx context.Context,
		branchCr *cloudv1.Branch,
		operator *clusterproxy.ClusterOperator,
	) (ClusterProxyFacade, bizerr.BizError)
	// GetServerlessProxy initializes a full serverless proxy and will call PD to fetch pdClusterID during setup.
	// Use it only for backup/restore APIs that require pd_cluster_id. Do not use it for K8s CR, DM connectivity,
	// or CDC keyspace APIs.
	GetServerlessProxy(ctx context.Context, baseURL string) (ServerlessClusterProxyFacade, bizerr.BizError)
	GetClusterProxyWithTimeout(
		ctx context.Context,
		cluster *tidbmgmtservicev1.Cluster,
		operator *clusterproxy.ClusterOperator,
		timeoutSec int,
	) (ClusterProxyFacade, bizerr.BizError)
	// GetServerlessHTTPProxy initializes a lightweight serverless HTTP proxy and will not call PD or fetch
	// pdClusterID during setup. Do not use it for backup/restore APIs.
	// When namespace or service is nil, the default serverless namespace/service will be used.
	GetServerlessHTTPProxy(
		ctx context.Context,
		clusterProxyURL string,
		namespace *string,
		service *string,
	) (ServerlessClusterProxyFacade, bizerr.BizError)
}

type ClusterProxyFacade interface {
	SetOperator(operator *clusterproxy.ClusterOperator) ClusterProxyFacade
	ExecuteSQL(ctx context.Context, sql string) bizerr.BizError
	ExecuteMultiSQL(ctx context.Context, sqls []string) bizerr.BizError
	InitCloudAdminPassword(ctx context.Context, cloudAdminPwd string) bizerr.BizError
	ChangePassword(ctx context.Context, username, newPwd string) bizerr.BizError
	QuerySQL(ctx context.Context, sql string) ([]byte, bizerr.BizError)
	OpenWebsocket(ctx context.Context) (clusterproxy.WebsocketConn, bizerr.BizError)
	GetProxyInfo(ctx context.Context) (clusterproxy.ClusterProxy, *clusterproxy.ClusterOperator)
	TestCDCConnection(ctx context.Context, model *clusterproxy.ConnectionModel) bizerr.BizError
}

type ServerlessClusterProxyFacade interface {
	ListBackups(ctx context.Context, req serverless.ListBackupRequest) (*serverless.ListBackupResponse, bizerr.BizError)
	RestoreServerlessCluster(ctx context.Context, req serverless.RestoreRequest) bizerr.BizError
	GetServerlessRestoreProgress(
		ctx context.Context,
		token string,
		restoreId uint64,
	) (*serverless.RestoreProgressResponse, bizerr.BizError)

	// TiCDC
	CreateServerlessCDC(ctx context.Context, keyspaceId uint32) bizerr.BizError
	DeleteServerlessCDC(ctx context.Context, keyspaceId uint32) bizerr.BizError
	CreateServerlessChangefeed(
		ctx context.Context,
		keyspaceId uint32,
		req *serverless.ChangefeedConfig,
	) (*serverless.Changefeed, bizerr.BizError)
	GetServerlessChangefeed(
		ctx context.Context,
		keyspaceId uint32,
		changefeedId string,
	) (*serverless.Changefeed, bizerr.BizError)
	ListServerlessChangefeeds(
		ctx context.Context,
		keyspaceId uint32,
		state *serverless.ChangefeedState,
	) (*serverless.ListChangefeedsResponse, bizerr.BizError)
	DeleteServerlessChangefeed(ctx context.Context, keyspaceId uint32, changefeedId string) bizerr.BizError
	UpdateServerlessChangefeed(
		ctx context.Context,
		keyspaceId uint32,
		changefeedId string,
		req *serverless.ChangefeedConfig,
	) (*serverless.Changefeed, bizerr.BizError)
	PauseServerlessChangefeed(ctx context.Context, keyspaceId uint32, changefeedId string) bizerr.BizError
	ResumeServerlessChangefeed(
		ctx context.Context,
		keyspaceId uint32,
		changefeedId string,
		req *serverless.ResumeChangefeedConfig,
	) bizerr.BizError
	GetServerlessGcSafePoint(ctx context.Context, keyspaceId uint32) (uint64, bizerr.BizError)
	CreateServerlessDMSource(ctx context.Context, req *dm.CreateSourceRequest) (*dm.Source, error)
	GetServerlessDMSource(ctx context.Context, name string) (*dm.Source, error)
	DeleteServerlessDMSource(ctx context.Context, name string) error
	CreateServerlessDMTask(ctx context.Context, req *dm.CreateTaskRequest) (*dm.OperateTaskResponse, error)
	DeleteServerlessDMTask(ctx context.Context, name string) error
	StartServerlessDMTask(ctx context.Context, name string, req *dm.StartTaskRequest) error
	StopServerlessDMTask(ctx context.Context, name string, req *dm.StopTaskRequest) error
	QueryServerlessDMTaskStatus(
		ctx context.Context,
		name string,
		sourceNameList []string,
	) (*dm.GetTaskStatusResponse, error)
	ListServerlessDMWorkers(ctx context.Context) (*dm.GetClusterWorkerListResponse, error)
	GetSourceTables(ctx context.Context, model *clusterproxy.ConnectionModel) ([]clusterproxy.TableInfo, error)
	TestConnection(ctx context.Context, model *clusterproxy.ConnectionModel) error
	CallK8sResource(ctx context.Context, req *clusterproxy.K8sResourceRequest) (any, error)
}

type ClusterProxyErr struct {
	bizerr.BizError
	SQLErrNo  uint16
	SQLErrMsg string
}

func NewClusterProxyErr(errNumber uint16, errMessage string) *ClusterProxyErr {
	out := &ClusterProxyErr{
		BizError:  bizerr.New(errorcode.ErrCodeSvcClusterProxy, "error is raised from tidb cluster"),
		SQLErrNo:  errNumber,
		SQLErrMsg: errMessage,
	}

	return out
}

func (e *ClusterProxyErr) Error() string {
	return fmt.Sprintf("TiDBErr(code=%d, msg=%s). error: %s", e.SQLErrNo, e.SQLErrMsg, e.BizError)
}

// ClusterSQLError exposes the raw SQL error number and message so that the
// errorcode package can decide whether it is safe to surface to the user.
// It implements errorcode.ClusterSQLErrorHolder.
func (e *ClusterProxyErr) ClusterSQLError() (uint16, string) {
	return e.SQLErrNo, e.SQLErrMsg
}
