package config

import (
	"flag"
	"fmt"
	"testing"

	"github.com/tidbcloud/observability/cmd/diagnosis-compute/pkg/types"
	"golang.org/x/sync/semaphore"
)

const (
	defaultParquetMaxFileBytes       int64 = 500 * 1024 * 1024
	defaultParquetMaxRowsPerRowGroup int64 = 10_000
	defaultParquetWriteBatchSize     int   = 512
	defaultClusterConcurrency        int64 = 8
	defaultConprofFirstHalvingDays   int64 = 7
	defaultConprofSecondHalvingDays  int64 = 15
	secondsPerDay                    int64 = 24 * 3600
)

var (
	ParquetMaxFileBytes       int64 = defaultParquetMaxFileBytes
	ParquetMaxRowsPerRowGroup int64 = defaultParquetMaxRowsPerRowGroup
	ParquetWriteBatchSize     int   = defaultParquetWriteBatchSize
)

func DeployType(info types.ClusterInfo) string {
	switch info.ClusterType {
	case types.ClusterTypeNextgen:
		return "premium"
	default:
		return "dedicated"
	}
}

var (
	Env                   string
	Vendor                string
	Region                string
	LogicalRegion         string
	Bucket                string
	s3concurrency         int64
	clusterConcurrency    int64
	Maxsecs               int64
	LocalMergedFileDir    string
	UseComponentWhitelist bool

	Spec            bool
	SpecAll         bool
	SpecTenantID    string
	SpecProjectID   string
	SpecClusterID   string
	SpecStorageID   string
	SpecClusterType int
	SpecOutputFile  string

	ConprofFirstHalvingDays  int64 = defaultConprofFirstHalvingDays
	ConprofSecondHalvingDays int64 = defaultConprofSecondHalvingDays
)

var (
	S3sem          *semaphore.Weighted
	ClusterTaskSem *semaphore.Weighted
)

var ComponentWhitelist = map[string]struct{}{
	"pd":                   {},
	"tidb":                 {},
	"tikv":                 {},
	"tiflash":              {},
	"ticdc":                {},
	"backup":               {},
	"dm-worker":            {},
	"dm-master":            {},
	"tidb-lightning":       {},
	"server":               {},
	"controller":           {},
	"regional-server":      {},
	"regionalserver":       {},
	"data-server":          {},
	"dataserver":           {},
	"agent-control-server": {},
	"vminsert":             {},
	"vmselect":             {},
	"vmstorage":            {},
}

func InitFlags() {
	if !testing.Testing() {
		flag.StringVar(&Env, "env", "prod", "env")
		flag.StringVar(&Vendor, "vendor", "aws", "vendor")
		flag.StringVar(&Region, "region", "us-east-1", "region")
		flag.StringVar(&LogicalRegion, "logical-region", "", "logical region")
		flag.StringVar(&Bucket, "bucket", "", "storage bucket name; when set, overrides env/region/logical-region derived bucket name")
		flag.Int64Var(&s3concurrency, "s3-concurrency", 100, "s3 download concurrency")
		flag.Int64Var(&clusterConcurrency, "cluster-concurrency", defaultClusterConcurrency, "max concurrent hourly cluster tasks")
		flag.Int64Var(&Maxsecs, "max-secs", 604800, "max secs") // 7d
		flag.BoolVar(&Spec, "spec", false, "spec")
		flag.BoolVar(&SpecAll, "spec-all", false, "spec all clusters in this region")
		flag.StringVar(&SpecTenantID, "spec-tenant-id", "", "spec tenant id")
		flag.StringVar(&SpecProjectID, "spec-project-id", "", "spec project id")
		flag.StringVar(&SpecClusterID, "spec-cluster-id", "", "spec cluster id")
		flag.StringVar(&SpecStorageID, "spec-storage-id", "", "spec storage id")
		flag.IntVar(&SpecClusterType, "spec-cluster-type", 0, "spec cluster type")
		flag.StringVar(&SpecOutputFile, "spec-output-file", "", "spec output file")
		flag.StringVar(&LocalMergedFileDir, "local-merged-file-dir", "./data", "local merged file dir")
		flag.BoolVar(&UseComponentWhitelist, "use-component-whitelist", false, "use component whitelist")
		flag.Int64Var(&ParquetMaxFileBytes, "parquet-max-file-bytes", defaultParquetMaxFileBytes, "max parquet file size in bytes")
		flag.Int64Var(&ParquetMaxRowsPerRowGroup, "parquet-max-rows-per-row-group", defaultParquetMaxRowsPerRowGroup, "max parquet rows per row group")
		flag.IntVar(&ParquetWriteBatchSize, "parquet-write-batch-size", defaultParquetWriteBatchSize, "parquet row batch size before flush to writer")
		flag.Int64Var(&ConprofFirstHalvingDays, "conprof-first-halving-days", defaultConprofFirstHalvingDays, "days after ingest to first conprof halving (delete samples where minute mod 2 != 0)")
		flag.Int64Var(&ConprofSecondHalvingDays, "conprof-second-halving-days", defaultConprofSecondHalvingDays, "days after ingest to second conprof halving (delete samples where minute mod 4 != 0)")
		flag.Parse()
		if err := validateConprofHalvingDays(ConprofFirstHalvingDays, ConprofSecondHalvingDays); err != nil {
			panic(err)
		}
		S3sem = semaphore.NewWeighted(s3concurrency)
		ClusterTaskSem = semaphore.NewWeighted(clusterConcurrency)
	}
}

func BucketName() string {
	if Bucket != "" {
		return Bucket
	}
	region := Region
	if LogicalRegion != "" {
		region = LogicalRegion
	}
	switch Env {
	case "prod":
		return fmt.Sprintf("o11y-prod-shared-%s", region)
	case "dev":
		return fmt.Sprintf("o11y-dev-shared-%s", region)
	default:
		return fmt.Sprintf("o11y-test-shared-%s", region)
	}
}

func S3Prefix() string {
	if Spec {
		return "diagnosis-test"
	}
	return "diagnosis"
}

func validateConprofHalvingDays(firstDays, secondDays int64) error {
	if firstDays <= 0 {
		return fmt.Errorf("conprof-first-halving-days must be positive, got %d", firstDays)
	}
	if secondDays <= 0 {
		return fmt.Errorf("conprof-second-halving-days must be positive, got %d", secondDays)
	}
	if secondDays <= firstDays {
		return fmt.Errorf("conprof-second-halving-days (%d) must be greater than conprof-first-halving-days (%d)", secondDays, firstDays)
	}
	return nil
}

// ConprofFirstHalvingAgeSeconds returns the age in seconds when first halving runs.
func ConprofFirstHalvingAgeSeconds() int64 {
	return ConprofFirstHalvingDays * secondsPerDay
}

// ConprofSecondHalvingAgeSeconds returns the age in seconds when second halving runs.
func ConprofSecondHalvingAgeSeconds() int64 {
	return ConprofSecondHalvingDays * secondsPerDay
}
