package globalserverlesscli

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
)

type clientImpl struct {
	clusterCli           globalserverlessv1.ServerlessServiceClient
	sqlUserCli           globalserverlessv1.SqlUserServiceClient
	providerCli          globalserverlessv1.CloudProviderServiceClient
	regionCli            globalserverlessv1.RegionServiceClient
	backupCli            globalserverlessv1.ServerlessPGBackupProxyServiceClient
	httpClient           *http.Client
	dataMigrationBaseURL string
}

func (c *clientImpl) CreateCluster(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceCreateRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ServerlessServiceCreateResponse, error) {
	return c.clusterCli.Create(ctx, in, opts...)
}

func (c *clientImpl) ListClusters(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceListRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ServerlessServiceListResponse, error) {
	return c.clusterCli.List(ctx, in, opts...)
}

func (c *clientImpl) CountClusters(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceCountClustersRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ServerlessServiceCountClustersResponse, error) {
	return c.clusterCli.CountClusters(ctx, in, opts...)
}

func (c *clientImpl) DeleteCluster(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceDeleteRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ServerlessServiceDeleteResponse, error) {
	return c.clusterCli.Delete(ctx, in, opts...)
}

func (c *clientImpl) UpdateCluster(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceUpdateRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ServerlessServiceUpdateResponse, error) {
	return c.clusterCli.Update(ctx, in, opts...)
}

func (c *clientImpl) ChangeSqlUserPasswd(
	ctx context.Context,
	in *globalserverlessv1.ChangeSqlUserPasswdRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ChangeSqlUserPasswdResponse, error) {
	return c.sqlUserCli.ChangeSqlUserPasswd(ctx, in, opts...)
}

func (c *clientImpl) RotateRootPassword(
	ctx context.Context,
	in *globalserverlessv1.RotateRootPasswordRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.RotateRootPasswordResponse, error) {
	return c.sqlUserCli.RotateRootPassword(ctx, in, opts...)
}

func (c *clientImpl) RedeemRootPassword(
	ctx context.Context,
	in *globalserverlessv1.RedeemRootPasswordRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.RedeemRootPasswordResponse, error) {
	return c.sqlUserCli.RedeemRootPassword(ctx, in, opts...)
}

func (c *clientImpl) CreatePGBackup(
	ctx context.Context,
	in *globalserverlessv1.CreatePGBackupRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.CreatePGBackupResponse, error) {
	return c.backupCli.CreatePGBackup(ctx, in, opts...)
}

func (c *clientImpl) ListPGBackups(
	ctx context.Context,
	in *globalserverlessv1.ListPGBackupsRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.ListPGBackupsResponse, error) {
	return c.backupCli.ListPGBackups(ctx, in, opts...)
}

func (c *clientImpl) GetPGBackup(
	ctx context.Context,
	in *globalserverlessv1.GetPGBackupRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.GetPGBackupResponse, error) {
	return c.backupCli.GetPGBackup(ctx, in, opts...)
}

func (c *clientImpl) GetPGBackupPolicy(
	ctx context.Context,
	in *globalserverlessv1.GetPGBackupPolicyRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.GetPGBackupPolicyResponse, error) {
	return c.backupCli.GetPGBackupPolicy(ctx, in, opts...)
}

func (c *clientImpl) UpdatePGBackupPolicy(
	ctx context.Context,
	in *globalserverlessv1.UpdatePGBackupPolicyRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.UpdatePGBackupPolicyResponse, error) {
	return c.backupCli.UpdatePGBackupPolicy(ctx, in, opts...)
}

func (c *clientImpl) ListCloudProviders(
	ctx context.Context,
	in *globalserverlessv1.CloudProviderServiceListRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.CloudProviderServiceListResponse, error) {
	return c.providerCli.List(ctx, in, opts...)
}

func (c *clientImpl) RegisterCloudProvider(
	ctx context.Context,
	in *globalserverlessv1.CloudProviderServiceRegisterRequest,
	opts ...grpc.CallOption,
) (*globalcommonv1.CloudProvider, error) {
	return c.providerCli.Register(ctx, in, opts...)
}

func (c *clientImpl) ListRegions(
	ctx context.Context,
	in *globalserverlessv1.RegionServiceListRequest,
	opts ...grpc.CallOption,
) (*globalserverlessv1.RegionServiceListResponse, error) {
	return c.regionCli.List(ctx, in, opts...)
}

func (c *clientImpl) GetRegion(
	ctx context.Context,
	in *globalserverlessv1.RegionServiceGetRequest,
	opts ...grpc.CallOption,
) (*globalcommonv1.Region, error) {
	return c.regionCli.Get(ctx, in, opts...)
}

func (c *clientImpl) RegisterRegion(
	ctx context.Context,
	in *globalserverlessv1.RegionServiceRegisterRequest,
	opts ...grpc.CallOption,
) (*globalcommonv1.Region, error) {
	return c.regionCli.Register(ctx, in, opts...)
}

func (c *clientImpl) UpdateRegion(
	ctx context.Context,
	in *globalserverlessv1.RegionServiceUpdateRequest,
	opts ...grpc.CallOption,
) (*globalcommonv1.Region, error) {
	return c.regionCli.Update(ctx, in, opts...)
}

func (c *clientImpl) DeleteRegion(
	ctx context.Context,
	in *globalserverlessv1.RegionServiceDeleteRequest,
	opts ...grpc.CallOption,
) (*emptypb.Empty, error) {
	return c.regionCli.Delete(ctx, in, opts...)
}

func (c *clientImpl) CreateDataMigrationTask(ctx context.Context, taskId, sourceDsn, targetDsn string) error {
	reqBody := dataMigrationCreateRequest{
		TaskID:               taskId,
		TaskConnectionOrigin: sourceDsn,
		TaskConnectionTarget: targetDsn,
	}
	bodyBytes, err := json.Marshal(reqBody)
	if err != nil {
		return fmt.Errorf("failed to marshal data migration create request: %w", err)
	}

	url := fmt.Sprintf("%s/api/migration", c.dataMigrationBaseURL)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes))
	if err != nil {
		return fmt.Errorf("failed to build data migration create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call data migration create API: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("data migration create API returned status %d: %s", resp.StatusCode, string(body))
	}
	return nil
}

// dataMigrationCreateRequest is the request body for creating a data migration task.
type dataMigrationCreateRequest struct {
	TaskID               string `json:"taskId"`
	TaskConnectionOrigin string `json:"taskConnectionOrigin"`
	TaskConnectionTarget string `json:"taskConnectionTarget"`
}

// dataMigrationStatusResponse is the response body for getting a data migration task status.
type dataMigrationStatusResponse struct {
	Status string `json:"status"`
}

// GetDataMigrationTask checks whether the data migration task is complete via the remote HTTP API.
// It returns true if the task has completed successfully, or false if still in progress.
func (c *clientImpl) CheckDataMigrationTask(ctx context.Context, taskId string) (bool, error) {
	url := fmt.Sprintf("%s/api/migration/%s", c.dataMigrationBaseURL, taskId)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
	if err != nil {
		return false, fmt.Errorf("failed to build data migration status request: %w", err)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return false, fmt.Errorf("failed to call data migration status API: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return false, fmt.Errorf("data migration status API returned status %d: %s", resp.StatusCode, string(body))
	}

	var statusResp dataMigrationStatusResponse
	if err := json.NewDecoder(resp.Body).Decode(&statusResp); err != nil {
		return false, fmt.Errorf("failed to decode data migration status response: %w", err)
	}

	return statusResp.Status == "SUCCESS", nil
}
