package tasks

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"strings"

	v1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/utils"
	"github.com/tidbcloud/observability/pkg/resourceworker/providers"
	"github.com/tidbcloud/observability/pkg/resourceworker/providers/types"
	"gopkg.in/yaml.v2"
)

const (
	diagnosisTaskPrefix        = "diagnosis/tasks/processor-dedicated/"
	diagnosisHistoryTaskPrefix = "diagnosis/tasks/history-tasks/"
)

var s3KeyTemplate = diagnosisTaskPrefix + "%s.yaml"
var historyTaskKeyTemplate = diagnosisHistoryTaskPrefix + "%s.yaml"

func NewO11yDiagnosisTasks(clientGroup utils.ClientGroup, s3Client providers.S3Client, region string, env string) {
	ctx := context.TODO()
	bucket := fmt.Sprintf("o11y-%s-shared-%s", env, region)
	if os.Getenv("BUCKET_NAME") != "" {
		bucket = os.Getenv("BUCKET_NAME")
	}
	clusterList := new(v1.ClusterList)
	err := clientGroup.Client.List(ctx, clusterList)
	if err != nil {
		panic(err)
	}

	tasksToUpload := make(map[string]O11yDiagnosisTask)
	for _, cluster := range clusterList.Items {
		task, ok := buildO11yDiagnosisTask(cluster)
		if !ok {
			continue
		}
		tasksToUpload[task.Meta.ClusterID] = task
	}

	if err := archiveStaleDiagnosisTasks(ctx, s3Client, bucket, tasksToUpload); err != nil {
		panic(err)
	}

	for clusterID, o11yTask := range tasksToUpload {
		data, err := yaml.Marshal(o11yTask)
		if err != nil {
			panic(err)
		}

		reader := bytes.NewReader(data)
		key := diagnosisTaskKey(clusterID)
		fmt.Printf("upload file to %s/%s\n", bucket, key)
		err = s3Client.Put(ctx, bucket, key, reader)
		if err != nil {
			panic(err)
		}
	}

	if err := removeActiveHistoryTasks(ctx, s3Client, bucket, tasksToUpload); err != nil {
		panic(err)
	}
}

func archiveStaleDiagnosisTasks(ctx context.Context, s3Client providers.S3Client, bucket string, activeTasks map[string]O11yDiagnosisTask) error {
	existingKeys, err := s3Client.ListObjects(ctx, bucket, diagnosisTaskPrefix, types.ListOptions{})
	if err != nil {
		return err
	}

	staleKeys := make([]string, 0)
	for _, key := range existingKeys {
		clusterID, ok := clusterIDFromTaskKey(diagnosisTaskPrefix, key)
		if !ok {
			continue
		}
		if _, exists := activeTasks[clusterID]; exists {
			continue
		}
		staleKeys = append(staleKeys, key)
	}

	if len(staleKeys) == 0 {
		fmt.Printf("no stale diagnosis tasks to archive from %s/%s\n", bucket, diagnosisTaskPrefix)
		return nil
	}

	fmt.Printf("archive %d stale diagnosis tasks from %s/%s to %s\n", len(staleKeys), bucket, diagnosisTaskPrefix, diagnosisHistoryTaskPrefix)
	for _, key := range staleKeys {
		clusterID, ok := clusterIDFromTaskKey(diagnosisTaskPrefix, key)
		if !ok {
			continue
		}
		if err := moveObject(ctx, s3Client, bucket, key, historyTaskKey(clusterID)); err != nil {
			return err
		}
	}
	return nil
}

func removeActiveHistoryTasks(ctx context.Context, s3Client providers.S3Client, bucket string, activeTasks map[string]O11yDiagnosisTask) error {
	if len(activeTasks) == 0 {
		return nil
	}
	existingKeys, err := s3Client.ListObjects(ctx, bucket, diagnosisHistoryTaskPrefix, types.ListOptions{})
	if err != nil {
		return err
	}
	toDelete := make([]string, 0)
	for _, key := range existingKeys {
		clusterID, ok := clusterIDFromTaskKey(diagnosisHistoryTaskPrefix, key)
		if !ok {
			continue
		}
		if _, exists := activeTasks[clusterID]; exists {
			toDelete = append(toDelete, key)
		}
	}
	if len(toDelete) == 0 {
		return nil
	}
	fmt.Printf("remove %d reactivated diagnosis tasks from %s/%s\n", len(toDelete), bucket, diagnosisHistoryTaskPrefix)
	return s3Client.Delete(ctx, bucket, toDelete...)
}

func loadHistoryDiagnosisTasks(ctx context.Context, s3Client providers.S3Client, bucket string) ([]O11yDiagnosisTask, error) {
	keys, err := s3Client.ListObjects(ctx, bucket, diagnosisHistoryTaskPrefix, types.ListOptions{})
	if err != nil {
		return nil, err
	}
	tasks := make([]O11yDiagnosisTask, 0, len(keys))
	for _, key := range keys {
		if _, ok := clusterIDFromTaskKey(diagnosisHistoryTaskPrefix, key); !ok {
			continue
		}
		task, err := getDiagnosisTask(ctx, s3Client, bucket, key)
		if err != nil {
			fmt.Printf("skip history diagnosis task %s: %v\n", key, err)
			continue
		}
		tasks = append(tasks, task)
	}
	return tasks, nil
}

func getDiagnosisTask(ctx context.Context, s3Client providers.S3Client, bucket, key string) (O11yDiagnosisTask, error) {
	rc, err := s3Client.Get(ctx, bucket, key)
	if err != nil {
		return O11yDiagnosisTask{}, err
	}
	defer func() {
		if rc != nil && *rc != nil {
			_ = (*rc).Close()
		}
	}()
	data, err := io.ReadAll(*rc)
	if err != nil {
		return O11yDiagnosisTask{}, err
	}
	var task O11yDiagnosisTask
	if err := yaml.Unmarshal(data, &task); err != nil {
		return O11yDiagnosisTask{}, err
	}
	return task, nil
}

func moveObject(ctx context.Context, s3Client providers.S3Client, bucket, src, dst string) error {
	task, err := getDiagnosisTask(ctx, s3Client, bucket, src)
	if err != nil {
		return err
	}
	data, err := yaml.Marshal(task)
	if err != nil {
		return err
	}
	if err := s3Client.Put(ctx, bucket, dst, bytes.NewReader(data)); err != nil {
		return err
	}
	return s3Client.Delete(ctx, bucket, src)
}

func diagnosisTaskKey(clusterID string) string {
	return fmt.Sprintf(s3KeyTemplate, clusterID)
}

func historyTaskKey(clusterID string) string {
	return fmt.Sprintf(historyTaskKeyTemplate, clusterID)
}

func clusterIDFromDiagnosisTaskKey(key string) (string, bool) {
	return clusterIDFromTaskKey(diagnosisTaskPrefix, key)
}

func clusterIDFromTaskKey(prefix, key string) (string, bool) {
	if !strings.HasPrefix(key, prefix) {
		return "", false
	}
	relative := strings.TrimPrefix(key, prefix)
	if relative == "" || strings.Contains(relative, "/") {
		return "", false
	}
	if !strings.HasSuffix(relative, ".yaml") {
		return "", false
	}
	clusterID := strings.TrimSuffix(relative, ".yaml")
	if clusterID == "" {
		return "", false
	}
	return clusterID, true
}

func buildO11yDiagnosisTask(cluster v1.Cluster) (O11yDiagnosisTask, bool) {
	taskEnv := "prod"
	nextGen := false
	switch cluster.Labels["observability.pingcap.com/bizType"] {
	case "tidbcloud":
		taskEnv = "prod"
		nextGen = false
	case "staging-tidbcloud":
		taskEnv = "staging"
		nextGen = false
	case "dev-tidbcloud":
		taskEnv = "dev"
		nextGen = false
	case "dev-tidbcloud-nextgen":
		taskEnv = "dev"
		nextGen = true
	case "staging-tidbcloud-nextgen":
		taskEnv = "staging"
		nextGen = true
	case "prod-tidbcloud-nextgen":
		nextGen = true
	case "byoc":
		taskEnv = "prod"
		nextGen = true
	default:
		return O11yDiagnosisTask{}, false
	}
	appID := cluster.Labels["observability.pingcap.com/applicationID"]
	if appID == "dataapp" ||
		appID == "k8s-infra" ||
		appID == "seed" ||
		appID == "base" {
		return O11yDiagnosisTask{}, false
	}
	if strings.HasPrefix(appID, "dp") {
		return O11yDiagnosisTask{}, false
	}

	var clusterType, projectID string
	if nextGen {
		clusterType = "nextgen"
	} else {
		clusterType = "dedicated"
		projectID = cluster.Labels["observability.pingcap.com/projectID"]
	}

	return O11yDiagnosisTask{
		Interval: 300,
		Meta: Meta{
			TenantID:        cluster.Labels["observability.pingcap.com/tenantID"],
			ProjectID:       projectID,
			ClusterID:       appID,
			O11yClusterID:   cluster.Name,
			DatabaseVersion: "",
			CreateTime:      int(cluster.CreationTimestamp.Time.Unix()),
			ClusterType:     clusterType,
			Provider:        cluster.Labels["observability.pingcap.com/vendor"],
			Region:          cluster.Labels["observability.pingcap.com/region"],
			Env:             taskEnv,
		},
		SubTasks: buildSubTasks(),
	}, true
}

type O11yDiagnosisTask struct {
	Interval int                    `yaml:"interval"`
	Meta     Meta                   `yaml:"meta"`
	SubTasks []O11yDiagnosisSubTask `yaml:"subtasks"`
}

type Meta struct {
	TenantID        string `yaml:"tenantID" json:"tenantID"`
	ProjectID       string `yaml:"projectID"`
	ClusterID       string `yaml:"clusterID"`
	O11yClusterID   string `yaml:"o11yClusterID"`
	DatabaseVersion string `yaml:"databaseVersion"`
	CreateTime      int    `yaml:"createTime"`
	ClusterType     string `yaml:"clusterType"`
	Provider        string `yaml:"provider"`
	Region          string `yaml:"region"`
	Env             string `yaml:"env"`
}

type O11yDiagnosisSubTask struct {
	Type     string `yaml:"type"`
	Category string `yaml:"category"`
	Name     string `yaml:"name"`
	Interval int    `yaml:"interval"`
	Hours    int    `yaml:"hours"`
}

func buildSubTasks() []O11yDiagnosisSubTask {
	return []O11yDiagnosisSubTask{
		{
			Type:     "metricsconverter",
			Category: "metrics",
			Name:     "metrics-incremental",
			Interval: 300,
		},
		{
			Type:     "slowlogconverter",
			Category: "slowlog",
			Name:     "slowlog-1h",
			Interval: 300,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-3h",
			Interval: 300,
			Hours:    3,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-6h",
			Interval: 300,
			Hours:    6,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-12h",
			Interval: 300,
			Hours:    12,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-24h",
			Interval: 300,
			Hours:    24,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-72h",
			Interval: 300,
			Hours:    72,
		},
		{
			Type:     "slowlogcompactor",
			Category: "slowlog",
			Name:     "slowlog-168h",
			Interval: 300,
			Hours:    168,
		},
	}
}
