package model

import (
	"context"

	"github.com/stretchr/testify/assert"
)

type DataflowNetworkType string

const (
	DataflowNetworkTypeNetworkTypePublic                DataflowNetworkType = "NetworkType_Public"
	DataflowNetworkTypeNetworkTypeVPCPeering            DataflowNetworkType = "NetworkType_VPC_Peering"
	DataflowNetworkTypeNetworkTypePrivateLink           DataflowNetworkType = "NetworkType_Private_Link"
	DataflowNetworkTypeNetworkTypePrivateServiceConnect DataflowNetworkType = "NetworkType_Private_Service_Connect"
)

type DataflowKafkaAuthType string

const (
	DataflowKafkaAuthTypeDISABLE         DataflowKafkaAuthType = "DISABLE"
	DataflowKafkaAuthTypeSASLPLAIN       DataflowKafkaAuthType = "SASL_PLAIN"
	DataflowKafkaAuthTypeSASLSCRAMSHA256 DataflowKafkaAuthType = "SASL_SCRAM_SHA_256"
	DataflowKafkaAuthTypeSASLSCRAMSHA512 DataflowKafkaAuthType = "SASL_SCRAM_SHA_512"
)

type DataflowPulsarAuthType string

const (
	DataflowPulsarAuthTypePULSARAUTHTYPEBASIC DataflowPulsarAuthType = "PULSAR_AUTH_TYPE_BASIC"
	DataflowPulsarAuthTypePULSARAUTHTYPETOKEN DataflowPulsarAuthType = "PULSAR_AUTH_TYPE_TOKEN"
)

type DataflowPulsarDestinationProtocol string

const (
	DataflowPulsarDestinationProtocolPULSARPROTOCOLPULSAR    DataflowPulsarDestinationProtocol = "PULSAR_PROTOCOL_PULSAR"
	DataflowPulsarDestinationProtocolPULSARPROTOCOLPULSARSSL DataflowPulsarDestinationProtocol = "PULSAR_PROTOCOL_PULSAR_SSL"
	DataflowPulsarDestinationProtocolPULSARPROTOCOLHTTP      DataflowPulsarDestinationProtocol = "PULSAR_PROTOCOL_HTTP"
	DataflowPulsarDestinationProtocolPULSARPROTOCOLHTTPS     DataflowPulsarDestinationProtocol = "PULSAR_PROTOCOL_HTTPS"
)

type CloudProviderType string

const (
	AWS      CloudProviderType = "aws"
	GCP      CloudProviderType = "gcp"
	AZURE    CloudProviderType = "azure"
	AliCloud CloudProviderType = "alicloud"
)

const (
	TiDBDedicated    = "tidb dedicated"
	TiDBPremium      = "tidb premium"
	KAFKA            = "kafka"
	MySQL            = "mysql"
	S3               = "s3"
	GCS              = "gcs"
	AzureBlobStorage = "azblob"
	TiDBServerless   = "tidb serverless"
	PULSAR           = "pulsar"
)

// DependencyCharacteristic defined what kinds dependency you want.
type DependencyCharacteristic interface {
	Type() string
	// Dimensions are the details characteristic of the dependency
	Dimensions() map[string]string
}

// DependencyRequirement is for the case to declare it's dependency requirement
type DependencyRequirement interface {
	DependencyCharacteristic
	// IsDedicated means the case want a dedicated dependency entity, won't share with other cases
	IsDedicated() bool
}

// DependencyEntity is a concrete dependency entity which can be assigned to cases
type DependencyEntity interface {
	DependencyCharacteristic
	// IsCreatedByDriver means the dependency entity is created by the case driver, case driver will destroy it after case running done
	IsCreatedByDriver() bool
}

// Dependency is about management of the same type of DependencyEntity
type Dependency interface {
	Type() string
	GetExistingUsableEntities() ([]DependencyEntity, error)
	ProvisionEntities(ctx context.Context, requirementStats EntitiesStats) (<-chan DependencyEntity, error)
	Delete(entities ...DependencyEntity) error
}

type EntitiesStatsItem struct {
	Dimensions map[string]string
	Number     int
}

type EntitiesStats []*EntitiesStatsItem

func (t *EntitiesStats) AddEntity(en DependencyEntity) {
	for _, it := range *t {
		if assert.ObjectsAreEqualValues(en.Dimensions(), it.Dimensions) {
			it.Number = it.Number + 1
			return
		}
	}
	*t = append(*t, &EntitiesStatsItem{
		Dimensions: en.Dimensions(),
		Number:     1,
	})
}

func (t *EntitiesStats) Gap(another EntitiesStats) EntitiesStats {
	var gap EntitiesStats
	for _, it := range *t {
		peer := another.GetItem(it.Dimensions)
		var number int
		if peer != nil {
			number = it.Number - peer.Number
		} else {
			number = it.Number
		}
		if number > 0 {
			gap = append(gap, &EntitiesStatsItem{
				Dimensions: it.Dimensions,
				Number:     number,
			})
		}
	}
	return gap
}

func (t *EntitiesStats) GetItem(dimensions map[string]string) *EntitiesStatsItem {
	for _, it := range *t {
		if assert.ObjectsAreEqualValues(dimensions, it.Dimensions) {
			return it
		}
	}
	return nil
}

type RequirementsStatsItem struct {
	Dimensions          map[string]string
	StatusOfIsDedicated []bool
}

func (t *RequirementsStatsItem) ToEntitiesStatsItem() *EntitiesStatsItem {
	number := 0
	hasShared := false
	for _, isDedicated := range t.StatusOfIsDedicated {
		if isDedicated {
			number++
		} else {
			hasShared = true
		}
	}
	if hasShared {
		number++
	}
	return &EntitiesStatsItem{Dimensions: t.Dimensions, Number: number}
}

type RequirementsStats []*RequirementsStatsItem

func (t *RequirementsStats) AddRequirement(req DependencyRequirement) {
	for _, it := range *t {
		if assert.ObjectsAreEqualValues(req.Dimensions(), it.Dimensions) {
			it.StatusOfIsDedicated = append(it.StatusOfIsDedicated, req.IsDedicated())
			return
		}
	}
	*t = append(*t, &RequirementsStatsItem{
		Dimensions:          req.Dimensions(),
		StatusOfIsDedicated: []bool{req.IsDedicated()},
	})
}

func (t *RequirementsStats) ToEntitiesStats() EntitiesStats {
	var stats EntitiesStats
	for _, it := range *t {
		stats = append(stats, it.ToEntitiesStatsItem())
	}
	return stats
}
