package regionalservice

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/log"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
)

func (c *ClusterHandler) AuthAndRouteForDS(ctx context.Context, req message.AuthRouteReq) (resp message.AuthRouteResp, err error) {
	if len(req.APIKey) > 0 {
		return c.AuthAndRouteByApiKey(ctx, req)
	} else if len(req.Path) > 0 {
		ctx = dealuseRDS(ctx, req.BizType)
		return c.AuthAndRouteByPath(ctx, req)
	}
	return message.AuthRouteResp{}, errors.New(errors.ParameterInvalid, "parameter path or apikey is required.")
}

func (c *ClusterHandler) AuthAndRouteByPath(ctx context.Context, req message.AuthRouteReq) (message.AuthRouteResp, error) {
	var clusterUniqueKey structs.ClusterUniqueKey
	var err error
	if req.UseO11yID {
		// if use o11yID, then parse path with o11yID
		o11yID, err := parsePathWithO11yID(req.Path)
		if err != nil {
			return message.AuthRouteResp{}, err
		}
		cluster, err := resourcemanager.GetClusterReaderWriter(ctx).GetCluster(ctx, o11yID)
		if err != nil {
			return message.AuthRouteResp{}, err
		}
		if cluster == nil {
			return message.AuthRouteResp{}, errors.New(errors.ClusterNotFound, "cluster not found by o11yID")
		}
		// this ensures the data access isolation.
		if req.ApplicationID != "" && cluster.Labels[constants.LabelApplicationID] != "" && cluster.Labels[constants.LabelApplicationID] != req.ApplicationID {
			return message.AuthRouteResp{}, errors.New(errors.ClusterNotFound, fmt.Sprintf("cluster not found by o11yID for cluster %s", req.ApplicationID))
		}
		return message.AuthRouteResp{
			VMRouteInfo: structs.VMRouteInfo{
				VMAccountID:   cluster.Spec.VictoriaAccount.AccountID,
				VMProjectID:   cluster.Spec.VictoriaAccount.ProjectID,
				VMClusterName: cluster.Spec.VictoriaAccount.VMClusterName,
			},
		}, nil
	}
	// if ignore project, then parse path without project
	if req.IgnoreProject {
		clusterUniqueKey, err = parsePathWithNoProject(req.Path)
		if err != nil {
			return message.AuthRouteResp{}, err
		}
	} else {
		clusterUniqueKey, err = parsePath(req.Path)
		if err != nil {
			return message.AuthRouteResp{}, err
		}
	}
	if clusterUniqueKey.BizType != "" {
		ctx = dealuseRDS(ctx, clusterUniqueKey.BizType)
	}
	clusters, err := resourcemanager.GetClusterReaderWriter(ctx).QueryClusters(ctx, clusterUniqueKey.TenantID, clusterUniqueKey.ProjectID, clusterUniqueKey.ApplicationID, clusterUniqueKey.BizType, message.ContinueRequest{})
	if err != nil {
		return message.AuthRouteResp{}, err
	} else if len(clusters.Items) == 0 {
		return message.AuthRouteResp{}, errors.New(errors.ClusterNotFound, "")
	} else {
		cluster := clusters.Items[0]
		return message.AuthRouteResp{
			VMRouteInfo: structs.VMRouteInfo{
				VMAccountID:   cluster.Spec.VictoriaAccount.AccountID,
				VMProjectID:   cluster.Spec.VictoriaAccount.ProjectID,
				VMClusterName: cluster.Spec.VictoriaAccount.VMClusterName,
			},
		}, nil
	}

}

// AuthAndRouteByApiKey not implemented yet, return error for now.
func (c *ClusterHandler) AuthAndRouteByApiKey(ctx context.Context, req message.AuthRouteReq) (message.AuthRouteResp, error) {
	log.Error("AuthByApiKey not implemented yet")
	return message.AuthRouteResp{}, errors.New(errors.ParameterInvalid, "AuthAndRouteByApiKey not implemented yet")
}

func parsePath(path string) (structs.ClusterUniqueKey, error) {
	// path example /external/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query
	// for external api, the path template is
	splitList := strings.Split(path, "/")
	if len(splitList) < 11 {
		return structs.ClusterUniqueKey{}, errors.NewErrorf(errors.ParameterInvalid, "The path %s is illegal", path)
	}

	for index, v := range splitList {
		if expected, ok := pathValidation[index]; ok {
			if expected != v {
				return structs.ClusterUniqueKey{}, errors.NewErrorf(errors.ParameterInvalid, "the value index '%d' in path should be '%s', but '%s'", index, expected, v)
			}
		}
	}
	return structs.ClusterUniqueKey{
		BizType:       splitList[bizTypeIndexInPath],
		TenantID:      splitList[tenantIDIndexInPath],
		ProjectID:     splitList[projectIDIndexInPath],
		ApplicationID: splitList[applicationIDIndexInPath],
	}, nil
}

func parsePathWithNoProject(path string) (structs.ClusterUniqueKey, error) {
	// path example /external/metrics/prod-devtier/tenant/1111111/application/3333333/api/v1/query
	// for external api, the path template is
	splitList := strings.Split(path, "/")
	if len(splitList) < 10 {
		return structs.ClusterUniqueKey{}, errors.NewErrorf(errors.ParameterInvalid, "The path %s is illegal", path)
	}

	for index, v := range splitList {
		if expected, ok := pathValidationWithNoProject[index]; ok {
			if expected != v {
				return structs.ClusterUniqueKey{}, errors.NewErrorf(errors.ParameterInvalid, "the value index '%d' in path should be '%s', but '%s'", index, expected, v)
			}
		}
	}
	return structs.ClusterUniqueKey{
		BizType:       splitList[bizTypeIndexInPathWithNoProject],
		TenantID:      splitList[tenantIDIndexInPathWithNoProject],
		ApplicationID: splitList[applicationIDIndexInPathWithNoProject],
	}, nil
}

func parsePathWithO11yID(path string) (string, error) {
	splitList := strings.Split(path, "/")

	// Check for old format: /internal/v2/metrics/{o11yID}/api/v1/query
	if len(splitList) > 1 && splitList[1] == "internal" {
		if len(splitList) < 7 {
			return "", errors.NewErrorf(errors.ParameterInvalid, "The path %s is illegal", path)
		}
		for index, v := range splitList {
			if expected, ok := pathValidationWithInternalO11yID[index]; ok {
				if expected != v {
					return "", errors.NewErrorf(errors.ParameterInvalid, "the value index '%d' in path should be '%s', but '%s'", index, expected, v)
				}
			}
		}
		return splitList[o11yIDIndexInPathWithInternalO11yID], nil
	}

	// New format: /019b2b01-0bef-744c-b9e0-d7f409ce4787/metrics/api/v1/query
	// for open api with o11yID, the path template is /{o11yID}/metrics/api/v1/...
	if len(splitList) < 5 {
		return "", errors.NewErrorf(errors.ParameterInvalid, "The path %s is illegal", path)
	}

	for index, v := range splitList {
		if expected, ok := pathValidationWithO11yID[index]; ok {
			if expected != v {
				return "", errors.NewErrorf(errors.ParameterInvalid, "the value index '%d' in path should be '%s', but '%s'", index, expected, v)
			}
		}
	}
	return splitList[o11yIDIndexInPathWithO11yID], nil
}

const bizTypeIndexInPath = 3
const tenantIDIndexInPath = 5
const projectIDIndexInPath = 7
const applicationIDIndexInPath = 9

var pathValidation = map[int]string{
	0:  "",
	1:  "external",
	2:  "metrics",
	4:  "tenant",
	6:  "project",
	8:  "application",
	10: "api",
	11: "v1",
}

const bizTypeIndexInPathWithNoProject = 3
const tenantIDIndexInPathWithNoProject = 5
const applicationIDIndexInPathWithNoProject = 7

var pathValidationWithNoProject = map[int]string{
	0: "",
	1: "external",
	2: "metrics",
	4: "tenant",
	6: "application",
	8: "api",
	9: "v1",
}

// o11yID is at index 1 in path: /{o11yID}/metrics/api/v1/...
const o11yIDIndexInPathWithO11yID = 1

var pathValidationWithO11yID = map[int]string{
	0: "",
	2: "metrics",
	3: "api",
	4: "v1",
}

// o11yID is at index 4 in path: /internal/v2/metrics/{o11yID}/api/v1/...
const o11yIDIndexInPathWithInternalO11yID = 4

var pathValidationWithInternalO11yID = map[int]string{
	0: "",
	1: "internal",
	2: "v2",
	3: "metrics",
	5: "api",
	6: "v1",
}
