package legacy

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

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/log"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/deploy"
	task "github.com/tidbcloud/observability/pkg/resourceworker/controllers/integrationtask/v2/task"
)

const (
	configMapNameTmpl = "job-manager-config-%s" // config-<tenant>
	configFileName    = "config.json"
)

func normalizeTaskType(taskType o11yv1.TaskType) string {
	if taskType == o11yv1.SqlStmtTask {
		return "sqlstatement"
	}
	return string(taskType)
}

type ConfigMap struct {
	*task.BaseTask
}

func NewConfigMap(ctx *task.Context) *ConfigMap {
	return &ConfigMap{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *ConfigMap) Name() string {
	return "ConfigMap"
}

func (t *ConfigMap) Create(ctx context.Context) deploy.Result {
	id, err := t.Identity()
	if err != nil {
		return deploy.Error(err)
	}

	if id.IsTenant() {
		if err := t.createOrUpdateTenantConfig(ctx, id); err != nil {
			return deploy.Error(err)
		}
		return deploy.Done()
	}

	if err := t.createOrUpdateJobManagerConfig(ctx, t.Task, id.Namespace, id.TenantID); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *ConfigMap) Delete(ctx context.Context) deploy.Result {
	id, err := t.Identity()
	if err != nil {
		return deploy.Error(err)
	}

	if id.IsTenant() {
		if err := t.deleteTenantConfig(ctx, id); err != nil {
			return deploy.Error(err)
		}
		return deploy.Done()
	}

	if err := t.removeClusterFromConfig(ctx, t.Task, id.Namespace, id.TenantID); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

// removeClusterFromConfig removes cluster-specific configuration from the shared ConfigMap
func (t *ConfigMap) removeClusterFromConfig(ctx context.Context, task *o11yv1.IntegrationTask, namespace, tenantID string) error {
	log := log.FromContext(ctx)

	configMapName := fmt.Sprintf(configMapNameTmpl, tenantID)
	key := client.ObjectKey{Name: configMapName, Namespace: namespace}

	configMap := &corev1.ConfigMap{}
	err := t.Client.Get(ctx, key, configMap)
	if err != nil {
		if client.IgnoreNotFound(err) == nil {
			// ConfigMap doesn't exist, nothing to clean up
			return nil
		}
		return fmt.Errorf("failed to get ConfigMap: %w", err)
	}

	// Parse existing configuration
	if configMap.Data == nil || configMap.Data["config.json"] == "" {
		// No configuration to remove
		return nil
	}

	var configData map[string]interface{}
	err = json.Unmarshal([]byte(configMap.Data["config.json"]), &configData)
	if err != nil {
		return fmt.Errorf("failed to parse existing config: %w", err)
	}

	// Navigate to tenant's clusters and remove the specific cluster
	if configData["tenants"] != nil {
		tenants := configData["tenants"].(map[string]interface{})
		if tenants[tenantID] != nil {
			tenantClusters := tenants[tenantID].(map[string]interface{})

			// Remove the cluster configuration
			delete(tenantClusters, task.Spec.ClusterID)

			// If tenant has no more clusters, remove the tenant
			if len(tenantClusters) == 0 {
				delete(tenants, tenantID)
				log.Info("Removed tenant from config as it has no more clusters", "tenantID", tenantID)
			}
		}
	}

	// Marshal the updated configuration
	configJSON, err := json.MarshalIndent(configData, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal updated config: %w", err)
	}

	// Update the ConfigMap
	configMap.Data["config.json"] = string(configJSON)
	err = t.Client.Update(ctx, configMap)
	if err != nil {
		return fmt.Errorf("failed to update ConfigMap: %w", err)
	}

	log.Info("Removed cluster from ConfigMap",
		"tenantID", tenantID,
		"clusterID", task.Spec.ClusterID)

	return nil
}

// createOrUpdateJobManagerConfig creates or updates the ConfigMap for job manager
func (t *ConfigMap) createOrUpdateJobManagerConfig(ctx context.Context, task *o11yv1.IntegrationTask, namespace, tenantID string) error {
	log := log.FromContext(ctx)

	// Use a shared ConfigMap for all tenants in the namespace
	configMapName := fmt.Sprintf(configMapNameTmpl, tenantID)

	// Check if ConfigMap already exists
	key := client.ObjectKey{Name: configMapName, Namespace: namespace}
	existingConfigMap := &corev1.ConfigMap{}
	getErr := t.Client.Get(ctx, key, existingConfigMap)

	// Create cluster configuration for this specific cluster
	clusterConfig := map[string]interface{}{
		"frequency":  task.Spec.Frequency,
		"taskType":   normalizeTaskType(task.Spec.TaskType),
		"dataFormat": task.Spec.DataFormat,
		"projectId":  task.Labels[constants.LabelProjectID],
		"deployType": resolveDeployType(task.Labels[constants.LabelBizType]),
	}

	// Build the hierarchical configuration structure
	var configData map[string]interface{}

	if getErr != nil && !apierrors.IsNotFound(getErr) {
		return fmt.Errorf("failed to get ConfigMap %s/%s: %w", key.Namespace, key.Name, getErr)
	}

	if apierrors.IsNotFound(getErr) || existingConfigMap.Data == nil || existingConfigMap.Data[configFileName] == "" {
		// ConfigMap doesn't exist, create it with initial structure
		log.Info("Creating new ConfigMap for slow query ETL", "configMapName", configMapName, "namespace", namespace)

		configData = map[string]interface{}{
			"tenants": map[string]interface{}{
				tenantID: map[string]interface{}{
					task.Spec.ClusterID: clusterConfig,
				},
			},
		}
	} else {
		// ConfigMap exists, parse existing data and update
		log.Info("Updating existing ConfigMap for slow query ETL", "configMapName", configMapName, "namespace", namespace)

		err := json.Unmarshal([]byte(existingConfigMap.Data["config.json"]), &configData)
		if err != nil {
			return fmt.Errorf("failed to parse existing config: %w", err)
		}

		// Ensure tenants section exists
		if configData["tenants"] == nil {
			configData["tenants"] = make(map[string]interface{})
		}

		tenants := configData["tenants"].(map[string]interface{})

		// Ensure tenant section exists
		if tenants[tenantID] == nil {
			tenants[tenantID] = make(map[string]interface{})
		}

		tenantClusters := tenants[tenantID].(map[string]interface{})

		// Add or update cluster configuration
		tenantClusters[task.Spec.ClusterID] = clusterConfig
	}

	// Marshal the complete configuration
	configJSON, err := json.MarshalIndent(configData, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal config data: %w", err)
	}

	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      configMapName,
			Namespace: namespace,
		},
	}

	_, err = controllerutil.CreateOrUpdate(ctx, t.Client, configMap, func() error {
		// Set labels
		if configMap.Labels == nil {
			configMap.Labels = make(map[string]string)
		}
		configMap.Labels["app"] = "slow-query-etl"
		configMap.Labels["version"] = "v1.0.0"

		// Set data
		if configMap.Data == nil {
			configMap.Data = make(map[string]string)
		}
		configMap.Data["config.json"] = string(configJSON)

		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to create or update ConfigMap: %w", err)
	}

	log.Info("ConfigMap updated successfully",
		"configMapName", configMapName,
		"tenantID", tenantID,
		"clusterID", task.Spec.ClusterID)

	return nil
}

// createOrUpdateTenantConfig writes a tenant-level config into a
// per-(tenant,dataType) ConfigMap. Unlike cluster scope (which lists per-cluster
// entries in a shared ConfigMap), a tenant pipeline covers all clusters under the
// tenant, so the config is a tenant-level object nested under tenants/<tenant>/all.
// This JSON is the control-plane -> ETL contract for tenant-scoped export.
func (t *ConfigMap) createOrUpdateTenantConfig(ctx context.Context, id task.Identity) error {
	log := log.FromContext(ctx)
	configMapName := fmt.Sprintf(configMapNameTmpl, id.ResourceSuffix())

	configData := map[string]interface{}{
		"tenants": map[string]interface{}{
			id.TenantID: map[string]interface{}{
				"all": map[string]interface{}{
					"dataFormat": t.Task.Spec.DataFormat,
					"frequency":  t.Task.Spec.Frequency,
					"taskType":   normalizeTaskType(id.TaskType),
					"deployType": resolveDeployType(t.Task.Labels[constants.LabelBizType]),
				},
			},
		},
	}
	configJSON, err := json.MarshalIndent(configData, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal tenant config data: %w", err)
	}

	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      configMapName,
			Namespace: id.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, t.Client, configMap, func() error {
		if configMap.Labels == nil {
			configMap.Labels = make(map[string]string)
		}
		configMap.Labels["app"] = "slow-query-etl"
		configMap.Labels["version"] = "v1.0.0"
		if configMap.Data == nil {
			configMap.Data = make(map[string]string)
		}
		configMap.Data[configFileName] = string(configJSON)
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to create or update tenant ConfigMap: %w", err)
	}

	log.Info("tenant ConfigMap updated successfully",
		"configMapName", configMapName,
		"tenantID", id.TenantID,
		"taskType", id.TaskType)
	return nil
}

// deleteTenantConfig deletes the per-(tenant,dataType) ConfigMap owned by the
// tenant task.
func (t *ConfigMap) deleteTenantConfig(ctx context.Context, id task.Identity) error {
	log := log.FromContext(ctx)
	configMapName := fmt.Sprintf(configMapNameTmpl, id.ResourceSuffix())
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      configMapName,
			Namespace: id.Namespace,
		},
	}
	if err := t.Client.Delete(ctx, configMap); err != nil {
		if client.IgnoreNotFound(err) == nil {
			return nil
		}
		return fmt.Errorf("failed to delete tenant ConfigMap: %w", err)
	}

	log.Info("tenant ConfigMap deleted",
		"configMapName", configMapName,
		"tenantID", id.TenantID,
		"taskType", id.TaskType)
	return nil
}

// extractTenantInfo extracts tenant ID and biz type from the task namespace or labels
func extractTenantInfo(task *o11yv1.IntegrationTask) (tenantID, bizType string) {
	if task.Labels != nil {
		if tid, ok := task.Labels[constants.LabelTenantID]; ok {
			tenantID = tid
		}
		if bt, ok := task.Labels[constants.LabelBizType]; ok {
			bizType = bt
		}
	}

	return tenantID, bizType
}
