package k8supgrade

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

	"github.com/tidbcloud/devops/entity/tenant"
)

// Selector is the interface for selecting k8s clusters.
type Selector interface {
	Name() string
	Matches(context.Context, *K8sInfo) bool
}

const (
	SelectorTypeNone            = ""
	SelectorTypeInternal        = "internal"
	SelectorTypeNonProd         = "non_prod"
	SelectorTypeByRegion        = "by_region"
	SelectorTypeByTenantIDs     = "by_tenant_ids"
	SelectorTypeByProjectIDs    = "by_project_ids"
	SelectorTypeByTiDBClusterID = "by_tidb_cluster_ids"
	SelectorTypeByK8sNames      = "by_k8s_names"
)

var errInvalidSelectorValue = errors.New("invalid selector_value")

func SelectorFromTask(task *Task) (Selector, error) {
	switch strings.ToLower(task.SelectorType) {
	case SelectorTypeNone:
		return &IncludeAll{}, nil
	case SelectorTypeInternal:
		return &Internal{}, nil
	case SelectorTypeNonProd:
		return &NonProdClusters{}, nil
	case SelectorTypeByRegion:
		region := strings.TrimSpace(task.SelectorValue)
		if strings.HasPrefix(region, "{") {
			var v struct {
				Region string `json:"region"`
			}
			if err := json.Unmarshal([]byte(task.SelectorValue), &v); err != nil {
				return nil, fmt.Errorf("%w: by_region: %w", errInvalidSelectorValue, err)
			}
			region = strings.TrimSpace(v.Region)
		}
		if region == "" {
			return nil, fmt.Errorf("%w: by_region: empty region", errInvalidSelectorValue)
		}
		return &ByRegion{Region: region}, nil
	case SelectorTypeByTenantIDs:
		var v struct {
			TenantIDs []string `json:"tenant_ids"`
		}
		if err := json.Unmarshal([]byte(task.SelectorValue), &v); err != nil {
			return nil, fmt.Errorf("%w: by_tenant_ids: %w", errInvalidSelectorValue, err)
		}
		return &ByTenantIDs{TenantIDs: v.TenantIDs}, nil
	case SelectorTypeByProjectIDs:
		var v struct {
			ProjectIDs []string `json:"project_ids"`
		}
		if err := json.Unmarshal([]byte(task.SelectorValue), &v); err != nil {
			return nil, fmt.Errorf("%w: by_project_ids: %w", errInvalidSelectorValue, err)
		}
		return &ByProjectIDs{ProjectIDs: v.ProjectIDs}, nil
	case SelectorTypeByTiDBClusterID:
		var v struct {
			TiDBClusterIDs []string `json:"tidb_cluster_ids"`
		}
		if err := json.Unmarshal([]byte(task.SelectorValue), &v); err != nil {
			return nil, fmt.Errorf("%w: by_tidb_cluster_ids: %w", errInvalidSelectorValue, err)
		}
		return &ByTiDBClusterIDs{TiDBClusterIDs: v.TiDBClusterIDs}, nil
	case SelectorTypeByK8sNames:
		var v struct {
			K8sNames []string `json:"k8s_names"`
		}
		if err := json.Unmarshal([]byte(task.SelectorValue), &v); err != nil {
			return nil, fmt.Errorf("%w: by_k8s_names: %w", errInvalidSelectorValue, err)
		}
		return &ByK8sNames{K8sNames: v.K8sNames}, nil
	default:
		return nil, fmt.Errorf("%w: unknown selector_type %q", errInvalidSelectorValue, task.SelectorType)
	}
}

// Internal selects clusters belong to internal tenants.
type Internal struct{}

var _ Selector = &Internal{}

func (s *Internal) Name() string {
	return "Internal"
}

func (s *Internal) Matches(_ context.Context, info *K8sInfo) bool {
	return info.TenantKind == string(tenant.TenantKindInternal)
}

// NonProdClusters selects clusters whose name contains "test", "dev" or "staging".
// If a k8s cluster has more than one tidb clusters, and not all of them whose name contains "test", "dev" or "staging",
// it will NOT be selected.
type NonProdClusters struct{}

var _ Selector = &NonProdClusters{}

var patterns = []string{
	"test",
	"dev",
	"staging",
}

func (s *NonProdClusters) Name() string {
	return "NonProdClusters"
}

func (s *NonProdClusters) Matches(_ context.Context, info *K8sInfo) bool {
	matches := true
	for _, cluster := range info.TiDBClusters {
		clusterName := strings.ToLower(cluster.Name)
		hasPattern := false
		for _, pattern := range patterns {
			if strings.Contains(clusterName, pattern) && !strings.Contains(clusterName, "prod") {
				hasPattern = true
				break
			}
		}
		if !hasPattern {
			matches = false
			break
		}
	}
	return matches
}

type ByRegion struct {
	Region string
}

var _ Selector = &ByRegion{}

func (b ByRegion) Name() string {
	return fmt.Sprintf("ByRegion-[%s]", b.Region)
}

func (b ByRegion) Matches(_ context.Context, info *K8sInfo) bool {
	return info.BusinessRegion == b.Region
}

type ByTenantIDs struct {
	TenantIDs []string
}

var _ Selector = &ByTenantIDs{}

func (b *ByTenantIDs) Name() string {
	return "ByTenantIDs"
}

func (b *ByTenantIDs) Matches(_ context.Context, info *K8sInfo) bool {
	for _, id := range b.TenantIDs {
		if strings.EqualFold(id, info.TenantID) {
			return true
		}
	}
	return false
}

type ByProjectIDs struct {
	ProjectIDs []string
}

var _ Selector = &ByProjectIDs{}

func (b *ByProjectIDs) Name() string {
	return "ByProjectIDs"
}

func (b *ByProjectIDs) Matches(_ context.Context, info *K8sInfo) bool {
	for _, id := range b.ProjectIDs {
		if strings.EqualFold(id, info.ProjectID) {
			return true
		}
	}
	return false
}

type ByTiDBClusterIDs struct {
	TiDBClusterIDs []string
}

var _ Selector = &ByTiDBClusterIDs{}

func (b *ByTiDBClusterIDs) Name() string {
	return "ByTiDBClusterIDs"
}

func (b *ByTiDBClusterIDs) Matches(_ context.Context, info *K8sInfo) bool {
	for _, c := range info.TiDBClusters {
		for _, id := range b.TiDBClusterIDs {
			if strings.EqualFold(id, c.ID) {
				return true
			}
		}
	}
	return false
}

type ByK8sNames struct {
	K8sNames []string
}

var _ Selector = &ByK8sNames{}

func (b *ByK8sNames) Name() string {
	return "ByK8sNames"
}

func (b *ByK8sNames) Matches(_ context.Context, info *K8sInfo) bool {
	for _, n := range b.K8sNames {
		if strings.EqualFold(n, info.K8sName) {
			return true
		}
	}
	return false
}

type ExcludeAll struct{}

var _ Selector = &ExcludeAll{}

func (e ExcludeAll) Name() string {
	return "ExcludeAll"
}

func (e ExcludeAll) Matches(_ context.Context, _ *K8sInfo) bool {
	return false
}

type IncludeAll struct{}

var _ Selector = &IncludeAll{}

func (i IncludeAll) Name() string {
	return "IncludeAll"
}

func (i IncludeAll) Matches(_ context.Context, _ *K8sInfo) bool {
	return true
}
