package proxy

import (
	"errors"
	"fmt"
	"strings"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/log"
	"github.com/pingcap-inc/clinic/pkg/o11y"
	rbac "github.com/pingcap-inc/clinic/pkg/rbac"
)

func nextGenHistoricalMetricsRegion(provider, physicalRegion, clusterID string) string {
	if provider != "aws" {
		return physicalRegion
	}
	switch physicalRegion {
	case "ap-south-1":
		return physicalRegion
	case "ap-southeast-2":
		return physicalRegion + "-ng"
	case "ap-southeast-1":
		return physicalRegion + "-premium"
	case "us-west-2":
		if !IsUsWest2HistoryNGHostCluster(clusterID) {
			return physicalRegion + "-premium"
		}
	}
	return physicalRegion
}

func nextGenHistoricalMetricsTarget(cli *o11y.Client, cluster *db.Cluster) (string, *o11y.ListClusterParam) {
	logicalRegion := nextGenHistoricalMetricsRegion(cluster.Provider, cluster.Region, cluster.ClusterID)
	return cli.GenDataServiceProxyHost(logicalRegion, cluster.Provider), cli.GetNextGenK8sClusterRequest(
		cluster.Provider,
		cluster.Region,
		fmt.Sprintf("%d", cluster.TenantID),
		cluster.DataPlaneName,
	)
}

// ProjectMetricHandler
type ProjectMetricHandler struct {
	rbac    rbac.Auth
	o11yCli *o11y.Client
	env     string
}

func NewProjectMetricHandler(rbac rbac.Auth, env string) *ProjectMetricHandler {
	return &ProjectMetricHandler{
		rbac: rbac,
		env:  env,
	}
}

func (handler *ProjectMetricHandler) SetO11y(o11yCli *o11y.Client) {
	handler.o11yCli = o11yCli
}

func (handler *ProjectMetricHandler) GinHandle(
	c *gin.Context,
	getClusterID func(c *gin.Context) string,
	getProjectID func(c *gin.Context) string,
	getControlPlaneInfo func(c *gin.Context) string,
	routerPathFunc func(string) string,
) {
	ctx := c.Request.Context()
	// get clusterID and projectID from cookie, must be used as metric query check and permission check
	clusterID, projectID := getClusterID(c), getProjectID(c)
	var o11yCluster *o11y.ClusterInfo
	var err error

	if projectID != "" {
		// get o11y cluster id based on projectID
		controlPlaneInfo := getControlPlaneInfo(c)
		o11yCluster, err = handler.handleByProjectID(c, projectID, controlPlaneInfo)
		if err != nil {
			log.Warn(ctx, "get project k8s cluster by project id failed", zap.Error(err))
		}
		if o11yCluster != nil {
			log.Info(ctx, "successfully resolved project k8s cluster by project id",
				zap.String("o11y_cluster_id", o11yCluster.ID))
		}
	}
	if o11yCluster == nil {
		var cluster *db.Cluster
		o11yCluster, cluster, err = handler.handleByClusterID(c, clusterID)
		if errors.Is(err, o11y.ErrNotFound) && cluster != nil {
			log.Info(ctx, "live project metrics target not found; using historical target",
				zap.String("operation", "resolve_project_metrics_historical_target"),
				zap.String("cluster_id", cluster.ClusterID),
				zap.String("provider", cluster.Provider),
				zap.String("physical_region", cluster.Region))
			switch cluster.DeployType {
			case o11y.NgHostType:
				targetHost, listParam := nextGenHistoricalMetricsTarget(handler.o11yCli, cluster)
				log.Info(ctx, "resolved NextGen historical project metrics target",
					zap.String("operation", "resolve_project_metrics_historical_target"),
					zap.String("cluster_id", cluster.ClusterID),
					zap.String("physical_region", cluster.Region),
					zap.String("logical_region", nextGenHistoricalMetricsRegion(cluster.Provider, cluster.Region, cluster.ClusterID)),
					zap.String("target_host", targetHost))
				proxyToO11yHistorical(c,
					handler.o11yCli,
					targetHost,
					listParam,
					routerPathFunc)
			case o11y.DedicatedType:
				region := cluster.Region
				if cluster.Provider == "aws" && cluster.Region == "us-west-2" && handler.env != "prod" && cluster.CreatedAt.After(o11yUswest2SwitchStagingDate) {
					region += "-staging"
				}
				targetHost := handler.o11yCli.GenDataServiceProxyHost(region, cluster.Provider)
				proxyToO11yHistorical(c,
					handler.o11yCli,
					targetHost,
					handler.o11yCli.GetProjectK8sClusterRequest(
						cluster.Provider,
						cluster.Region,
						fmt.Sprintf("%d", cluster.TenantID),
						cluster.ProjectID,
					),
					routerPathFunc)
			default:
				_ = c.Error(error2.ErrBadRequest.New("historical metrics query for this cluster type is not supported"))
			}
			return
		} else if err != nil {
			log.Error(ctx, "get project k8s cluster by cluster id failed", zap.Error(err))
			_ = c.Error(err)
			return
		}
		log.Info(ctx, "successfully resolved project k8s cluster by cluster id",
			zap.String("o11y_cluster_id", o11yCluster.ID))
	}
	proxyToO11yCluster(c, o11yCluster, routerPathFunc)
}

func (handler *ProjectMetricHandler) handleByClusterID(
	c *gin.Context,
	clusterID string,
) (*o11y.ClusterInfo, *db.Cluster, error) {
	if clusterID == "" {
		return nil, nil, error2.ErrNotFound.New("cluster is not found")
	}

	_, cluster, ok, err := common.CanReadCloudCluster(c, handler.rbac, clusterID)
	if err != nil {
		return nil, cluster, error2.ErrNotFound.New("%s", err.Error())
	}
	if !ok {
		return nil, cluster, error2.ErrForbidden.New("access cluster failed")
	}

	var o11yCluster *o11y.ClusterInfo
	ctx := c.Request.Context()
	switch cluster.DeployType {
	case "nextgen-host":
		o11yCluster, err = handler.o11yCli.GetNextGenK8sCluster(
			ctx,
			cluster.Provider,
			cluster.Region,
			fmt.Sprintf("%d", cluster.TenantID),
			cluster.DataPlaneName,
		)
	case "byoc-host":
		o11yCluster, err = handler.o11yCli.GetByocK8sCluster(
			ctx,
			cluster.Provider,
			cluster.Region,
			fmt.Sprintf("%d", cluster.TenantID),
			cluster.DataPlaneName,
		)
	default:
		o11yCluster, err = handler.o11yCli.GetProjectK8sCluster(
			ctx,
			cluster.Provider,
			cluster.Region,
			fmt.Sprintf("%d", cluster.TenantID),
			cluster.ProjectID,
		)
	}

	if err != nil {
		log.Error(ctx, "get project k8s cluster by cluster id failed",
			zap.Error(err),
			zap.String("region", cluster.Region),
			zap.String("vendor", cluster.Provider))
		return nil, cluster, err
	}
	return o11yCluster, cluster, nil
}

func (handler *ProjectMetricHandler) handleByProjectID(
	c *gin.Context,
	projectID string,
	controlPlaneInfo string,
) (*o11y.ClusterInfo, error) {
	// get provider and region from control plane info
	if len(controlPlaneInfo) == 0 {
		return nil, error2.ErrNotFound.New("control_plane_info is not found")
	}
	provider, region, err := parseControlPlaneInfo(controlPlaneInfo)
	if err != nil {
		log.Error(c.Request.Context(), "parse control_plane_info failed",
			zap.Error(err),
			zap.String("project_id", projectID))
		return nil, error2.ErrNotFound.New("%s", err.Error())
	}

	_, project, ok, err := common.CanReadCloudProject(c, handler.rbac, projectID)
	if err != nil || project == nil {
		return nil, error2.ErrNotFound.New("%s", err.Error())
	}

	if !ok {
		return nil, error2.ErrForbidden.New("access project failed")
	}

	o11yCluster, err := handler.o11yCli.GetProjectK8sCluster(
		c.Request.Context(),
		provider,
		region,
		fmt.Sprintf("%d", project.TenantID),
		projectID,
	)
	if err != nil {
		log.Error(c.Request.Context(), "get project k8s cluster by project id failed",
			zap.Error(err),
			zap.String("region", region),
			zap.String("vendor", provider),
			zap.String("project_id", projectID))
		return nil, error2.ErrInternal.New("get project k8s cluster by project id failed")
	}
	return o11yCluster, nil
}

func parseControlPlaneInfo(info string) (string, string, error) {
	parts := strings.Split(info, "/")
	if len(parts) < 3 {
		return "", "", fmt.Errorf("invalid control_plane_info format")
	}

	middlePart := parts[1]

	firstDashIndex := strings.Index(middlePart, "-")
	if firstDashIndex == -1 {
		return "", "", fmt.Errorf("invalid control_plane_info format")
	}

	provider := middlePart[:firstDashIndex]
	region := middlePart[firstDashIndex+1:]

	return provider, region, nil
}

func parseControlPlaneInfoV2(info string) (string, string, string, string, error) {
	parts := strings.Split(info, "/")
	if len(parts) < 3 {
		return "", "", "", "", fmt.Errorf("invalid control_plane_info format")
	}

	middlePart := parts[1]

	firstDashIndex := strings.Index(middlePart, "-")
	if firstDashIndex == -1 {
		return "", "", "", "", fmt.Errorf("invalid control_plane_info format")
	}

	provider := middlePart[:firstDashIndex]
	region := middlePart[firstDashIndex+1:]

	return parts[0], provider, region, parts[2], nil
}
