package o11y

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/log"
)

var ErrNotFound = errors.New("not found")

func (cli *Client) setO11yClusterListToCache(ctx context.Context, param *ListClusterParam, clusterList []ClusterInfo) {
	log.Info(ctx, "set o11y cluster list to cache", zap.Any("param", param), zap.Any("clusters", clusterList))

	jsonData, err := json.Marshal(param)
	if err != nil {
		log.Error(ctx, "marshal cluster list failed", zap.Error(err))
		return
	}
	// set cache with json data
	cli.o11yClusterListCache.Set(string(jsonData), clusterList, 300*time.Second)
}

func (cli *Client) getO11yClusterListFromCache(ctx context.Context, param *ListClusterParam) ([]ClusterInfo, bool) {
	jsonData, err := json.Marshal(param)
	if err != nil {
		log.Error(ctx, "marshal cluster list param failed", zap.Error(err))
		return nil, false
	}
	item, found := cli.o11yClusterListCache.Get(string(jsonData))
	if !found || item == nil {
		return nil, false
	}
	data, ok := item.([]ClusterInfo)
	if !ok {
		return nil, false
	}
	return data, true
}

func (cli *Client) SetSecondO11yClient(o11yCli *Client) {
	if o11yCli == nil {
		log.PlainWarn("set second o11y client with nil")
		return
	}
	cli.secondClient = o11yCli
}

func (cli *Client) ListClusterWithCache(ctx context.Context, param *ListClusterParam) ([]ClusterInfo, error) {
	if infos, found := cli.getO11yClusterListFromCache(ctx, param); found {
		log.Info(ctx, "list o11y cluster from cache", zap.Any("param", param), zap.Any("clusters", infos))
		return infos, nil
	}

	resp, err := cli.ListCluster(ctx, param)
	if err != nil {
		log.Error(ctx, "failed to list o11y cluster", zap.Any("param", param), zap.Any("clusters", resp))
		return nil, fmt.Errorf("list o11y cluster failed: %w", err)
	}
	if len(resp.Clusters) == 0 {
		return nil, fmt.Errorf("%w: no o11y cluster found", ErrNotFound)
	}
	cli.setO11yClusterListToCache(ctx, param, resp.Clusters)

	cluserId := resp.Clusters[0].ID

	log.Info(ctx, "list o11y cluster  "+cluserId, zap.Any("param", param), zap.Any("clusters", resp.Clusters))
	return resp.Clusters, nil
}

func (cli *Client) GetProjectK8sClusterRequest(vendor, region, orgID, o11yProjectID string) *ListClusterParam {
	return &ListClusterParam{
		BizType:       cli.DedicatedBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     o11yProjectID,
		ApplicationID: "k8s-infra",
		ShowDeleted:   true,
	}
}

func (cli *Client) GetProjectK8sCluster(ctx context.Context, vendor, region, orgID, o11yProjectID string) (*ClusterInfo, error) {
	param := cli.GetProjectK8sClusterRequest(vendor, region, orgID, o11yProjectID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get project k8s cluster failed: %w", err)
	}
	if len(infos) == 0 {
		return nil, fmt.Errorf("%w: no k8s cluster found for vendor: %s, region: %s, orgID: %s, o11yProjectID: %s", ErrNotFound, vendor, region, orgID, o11yProjectID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple k8s clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one k8s cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetNextGenK8sClusterRequest(vendor, region, orgID, dataplaneID string) *ListClusterParam {
	if orgID == "0" {
		orgID = "shared-multi-tenant"
	}

	return &ListClusterParam{
		BizType:       cli.NextGenBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: dataplaneID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetNextGenK8sCluster(ctx context.Context, vendor, region, orgID, dataplaneID string) (*ClusterInfo, error) {
	param := cli.GetNextGenK8sClusterRequest(vendor, region, orgID, dataplaneID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get next gen k8s o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetNextGenK8sCluster(ctx, vendor, region, orgID, dataplaneID)
		}
		return nil, fmt.Errorf("%w: no next gen k8s o11y cluster found for vendor: %s, region: %s, orgID: %s, dataplaneID: %s", ErrNotFound, vendor, region, orgID, dataplaneID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple next gen k8s clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one next gen k8s o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetDedicatedTidbClusterRequest(vendor, region, orgID, clusterID string) *ListClusterParam {
	return &ListClusterParam{
		BizType:  cli.DedicatedBizTypeWithEnv(),
		Vendor:   vendor,
		Region:   region,
		TenantID: orgID,
		// Do not filter by the DBaaS project ID: a user cluster can migrate to
		// another DBaaS project while its O11Y record keeps the original value.
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetDedicatedTidbCluster(ctx context.Context, vendor, region, orgID, clusterID string) (*ClusterInfo, error) {
	param := cli.GetDedicatedTidbClusterRequest(vendor, region, orgID, clusterID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get tidb o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		return nil, fmt.Errorf("%w: no tidb o11y cluster found for vendor: %s, region: %s, orgID: %s, clusterID: %s", ErrNotFound, vendor, region, orgID, clusterID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple tidb o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one tidb o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetServerlessTidbCluster(ctx context.Context, vendor, region, tenantID, clusterID string) ([]ClusterInfo, error) {
	param := &ListClusterParam{
		BizType:       cli.DevTierBizTypeWithEnv(),
		Region:        region,
		Vendor:        vendor,
		TenantID:      tenantID,
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get serverless o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		return nil, fmt.Errorf("%w: o11y urls: no serverless o11y cluster found for bizType: %s, vendor: %s, region: %s",
			ErrNotFound, param.BizType, vendor, region)
	}
	return infos, nil
}

func (cli *Client) GetServerlessControlPlaneCluster(ctx context.Context, vendor, region, controlPlaneProjectID, applicationID string) (*ClusterInfo, error) {
	bizType := cli.DevTierBizTypeWithEnv()
	param := &ListClusterParam{
		BizType:       bizType,
		Region:        region,
		Vendor:        vendor,
		TenantID:      bizType,
		ProjectID:     controlPlaneProjectID,
		ApplicationID: applicationID,
		ShowDeleted:   true,
	}
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get serverless control plane o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		return nil, fmt.Errorf("%w: o11y urls: no serverless control plane o11y cluster found for bizType: %s, vendor: %s, region: %s",
			ErrNotFound, param.BizType, vendor, region)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple serverless control plane o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one serverless control plane o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetNextGenTidbClusterRequest(vendor, region, orgID, clusterID string) *ListClusterParam {
	if orgID == "0" {
		orgID = "shared-multi-tenant"
	}

	return &ListClusterParam{
		BizType:       cli.NextGenBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetNextGenTidbCluster(ctx context.Context, vendor, region, orgID, clusterID string) (*ClusterInfo, error) {
	param := cli.GetNextGenTidbClusterRequest(vendor, region, orgID, clusterID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get next gen tidb o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetNextGenTidbCluster(ctx, vendor, region, orgID, clusterID)
		}
		return nil, fmt.Errorf("%w: no next gen tidb o11y cluster found for vendor: %s, region: %s, orgID: %s, clusterID: %s", ErrNotFound, vendor, region, orgID, clusterID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple next gen tidb o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one next gen tidb o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetNextGenHostTidbClusterRequest(vendor, region, orgID, clusterID string) *ListClusterParam {
	if orgID == "0" {
		orgID = "shared-multi-tenant"
	}

	return &ListClusterParam{
		BizType:       cli.NextGenBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetNextGenHostTidbCluster(ctx context.Context, vendor, region, orgID, clusterID string) (*ClusterInfo, error) {
	param := cli.GetNextGenHostTidbClusterRequest(vendor, region, orgID, clusterID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get next gen host o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetNextGenHostTidbCluster(ctx, vendor, region, orgID, clusterID)
		}
		return nil, fmt.Errorf("%w: no next gen host o11y cluster found for vendor: %s, region: %s, orgID: %s, clusterID: %s", ErrNotFound, vendor, region, orgID, clusterID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple next gen host o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one next gen host o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetByocTidbClusterRequest(vendor, region, orgID, clusterID string) *ListClusterParam {
	return &ListClusterParam{
		BizType:       cli.ByocBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetByocTidbCluster(ctx context.Context, vendor, region, orgID, clusterID string) (*ClusterInfo, error) {
	param := cli.GetByocTidbClusterRequest(vendor, region, orgID, clusterID)
	log.Info(ctx, "get byoc tidb o11y cluster", zap.Any("request_param", param))
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get byoc tidb o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		log.Info(ctx, "no byoc tidb o11y cluster found from cache , for vendor: %s, region: %s, orgID: %s, clusterID: %s", zap.String("vendor", vendor), zap.String("region", region), zap.String("orgID", orgID), zap.String("clusterID", clusterID))
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetByocTidbCluster(ctx, vendor, region, orgID, clusterID)
		}
		return nil, fmt.Errorf("%w: no byoc tidb o11y cluster found for vendor: %s, region: %s, orgID: %s, clusterID: %s", ErrNotFound, vendor, region, orgID, clusterID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple byoc tidb o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one byoc tidb o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetByocHostClusterRequest(vendor, region, orgID, clusterID string) *ListClusterParam {
	return &ListClusterParam{
		BizType:       cli.ByocBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: clusterID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetByocHostCluster(ctx context.Context, vendor, region, orgID, clusterID string) (*ClusterInfo, error) {
	param := cli.GetByocHostClusterRequest(vendor, region, orgID, clusterID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get byoc host o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetByocHostCluster(ctx, vendor, region, orgID, clusterID)
		}
		return nil, fmt.Errorf("%w: no byoc host o11y cluster found for vendor: %s, region: %s, orgID: %s, clusterID: %s", ErrNotFound, vendor, region, orgID, clusterID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple byoc host o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one byoc host o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetByocK8sClusterRequest(vendor, region, orgID, dataplaneID string) *ListClusterParam {
	return &ListClusterParam{
		BizType:       cli.ByocBizTypeWithEnv(),
		Vendor:        vendor,
		Region:        region,
		TenantID:      orgID,
		ProjectID:     "", // ProjectID is not used for next gen tidb cluster
		ApplicationID: dataplaneID,
		ShowDeleted:   true,
	}
}

func (cli *Client) GetByocK8sCluster(ctx context.Context, vendor, region, orgID, dataplaneID string) (*ClusterInfo, error) {
	param := cli.GetByocK8sClusterRequest(vendor, region, orgID, dataplaneID)
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get byoc k8s o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		if cli.secondClient != nil {
			// try to get from second client
			return cli.secondClient.GetByocK8sCluster(ctx, vendor, region, orgID, dataplaneID)
		}
		return nil, fmt.Errorf("%w: no byoc k8s o11y cluster found for vendor: %s, region: %s, orgID: %s, dataplaneID: %s", ErrNotFound, vendor, region, orgID, dataplaneID)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple byoc k8s clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one byoc k8s o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) getSeedCluster(ctx context.Context, vendor, region, applicationID, bizType string) (*ClusterInfo, error) {
	if applicationID == "" {
		applicationID = SeedID
	}
	param := &ListClusterParam{
		BizType:       bizType,
		Region:        region,
		Vendor:        vendor,
		TenantID:      cli.BaseShootTenantWithEnv(),
		ProjectID:     SeedID,
		ApplicationID: applicationID,
		ShowDeleted:   true,
	}
	infos, err := cli.ListClusterWithCache(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("get seed o11y cluster failed: %w", err)
	}
	if len(infos) == 0 {
		return nil, fmt.Errorf("%w: no seed o11y cluster found", ErrNotFound)
	}
	if len(infos) > 1 {
		log.Warn(ctx, "found multiple seed o11y clusters",
			zap.Int("count", len(infos)),
			zap.Any("request_param", param),
			zap.Any("clusters", infos))
		return nil, errors.New("more than one seed o11y cluster found")
	}
	return &infos[0], nil
}

func (cli *Client) GetDedicatedSeedCluster(ctx context.Context, vendor, region, applicationID string) (*ClusterInfo, error) {
	return cli.getSeedCluster(ctx, vendor, region, applicationID, cli.DedicatedBizTypeWithEnv())
}

func (cli *Client) GetNextGenSeedCluster(ctx context.Context, vendor, region, applicationID string) (*ClusterInfo, error) {
	return cli.getSeedCluster(ctx, vendor, region, applicationID, cli.NextGenBizTypeWithEnv())
}

// GetSeedCluster preserves the legacy dedicated seed lookup for callers that
// have not migrated to an explicit seed biz type.
// Deprecated: use GetDedicatedSeedCluster or GetNextGenSeedCluster.
func (cli *Client) GetSeedCluster(ctx context.Context, vendor, region, applicationID string) (*ClusterInfo, error) {
	return cli.GetDedicatedSeedCluster(ctx, vendor, region, applicationID)
}
