package app

import (
	"fmt"
	"io"
	"os"

	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/framework"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/input_k8s_v1"
	input_o11y_metrics_v1 "github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/input_o11y_v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/input_tikv_capacity_v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/output_blackhole"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/output_capacity_reservation_v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/output_direct_hpa_v1"
	scaler_k8s_v1 "github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/scaler_k8s_res_based_v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/scaler_o11y_res_based_v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/modules/scaler_tikv_capacity_v1"
	"github.com/tidbcloud/pkgv2/bizerr"
	"gopkg.in/yaml.v2"
)

const DefaultConfigPath = "/etc/ng_autoscaler/config.yaml"

var ErrNilConfig = bizerr.New(bizerr.ErrCodePanic, "config is nil")

type AutoscaleConfig struct {
	Inputs         []InputConfig         `yaml:"inputs"`
	Outputs        []OutputConfig        `yaml:"outputs"`
	Scalers        []ScalerConfig        `yaml:"scalers"`
	ClusterScalers []ClusterScalerConfig `yaml:"clusterScalers"`
}

type InputConfig struct {
	UniqName         string         `yaml:"uniqName"`
	Type             string         `yaml:"type"`
	InterfaceVersion string         `yaml:"interfaceVersion"`
	Config           map[string]any `yaml:"config"`
}

type OutputConfig struct {
	UniqName         string         `yaml:"uniqName"`
	Type             string         `yaml:"type"`
	InterfaceVersion string         `yaml:"interfaceVersion"`
	Config           map[string]any `yaml:"config"`
}

type ScalerConfig struct {
	UniqName         string         `yaml:"uniqName"`
	Type             string         `yaml:"type"`
	InterfaceVersion string         `yaml:"interfaceVersion"`
	InputUniqName    string         `yaml:"inputUniqName"`
	OutputUniqName   string         `yaml:"outputUniqName"`
	DefaultConfig    map[string]any `yaml:"defaultConfig"`
	InputConfig      map[string]any `yaml:"inputConfig"`
	OutputConfig     map[string]any `yaml:"outputConfig"`
}

type ClusterScalerConfig struct {
	ScalerUniqName  string         `yaml:"scalerUniqName"`
	TargetClusterId string         `yaml:"targetClusterId"`
	ExecutorName    string         `yaml:"executorName"`
	ScalerConfig    map[string]any `yaml:"scalerConfig"`
	ExecutorConfig  map[string]any `yaml:"executorConfig"`
	InputConfig     map[string]any `yaml:"inputConfig"`
	OutputConfig    map[string]any `yaml:"outputConfig"`
}

type configFile struct {
	Autoscale AutoscaleConfig `yaml:"autoscale"`
}

func LoadConfigFromFile(cfgFilePath string) (*AutoscaleConfig, error) {
	file, err := os.OpenFile(cfgFilePath, os.O_RDONLY, 0o0644)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodePanic, "failed to open config file: %v", err)
	}
	defer func() { _ = file.Close() }()

	content, err := io.ReadAll(file)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodePanic, "failed to read config file: %v", err)
	}

	cfg := &configFile{}
	err = yaml.UnmarshalStrict(content, cfg)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodePanic, "failed to unmarshal yaml config file: %v", err)
	}

	return &cfg.Autoscale, nil
}

func parseInputConfig(cfg map[string]any, inputType string) (framework.ConfigData, error) {
	if cfg == nil {
		return nil, ErrNilConfig
	}
	switch inputType {
	case string(framework.InputProviderMockV1IdStr):
		var c framework.InputProviderMockConfigV1
		content, err := yaml.Marshal(cfg)
		if err != nil {
			return nil, err
		}
		if err := yaml.UnmarshalStrict(content, &c); err != nil {
			return nil, err
		}
		return &c, nil
	case string(input_k8s_v1.InputK8sV1IdStr):
		return input_k8s_v1.ParseConfig(cfg)
	case string(input_o11y_metrics_v1.InputO11yMetricsV1IdStr):
		return input_o11y_metrics_v1.ParseConfig(cfg)
	case string(input_tikv_capacity_v1.InputTiKVCapacityV1IdStr):
		return input_tikv_capacity_v1.ParseConfig(cfg)
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown input type: %s", inputType)
	}
}

func parseOutputConfig(cfg map[string]any, outputType string) (framework.ConfigData, error) {
	switch outputType {
	case string(framework.OutputMockEmptyIdStr):
		return &framework.OutputMockEmptyConfig{}, nil
	case string(output_blackhole.OutputBlackholeIdStr):
		if cfg == nil {
			return nil, ErrNilConfig
		}
		return output_blackhole.ParseConfig(cfg)
	case string(output_direct_hpa_v1.OutputDirectHPAV1IdStr):
		if cfg == nil {
			return nil, ErrNilConfig
		}
		return output_direct_hpa_v1.ParseConfig(cfg)
	case string(output_capacity_reservation_v1.OutputCapacityReservationV1IdStr):
		return output_capacity_reservation_v1.ParseConfig(cfg)
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown output type: %s", outputType)
	}
}

func getComponentScalerType(scalerType string) framework.PhasedComponentScalerType {
	switch framework.ClusterScalerType(scalerType) {
	case ScalingRouteK8s2BlackholeByResourceUsageBasedType,
		ScalingRouteK8s2HPAByResourceUsageBasedType,
		ScalingRouteO11y2BlackholeByResourceUsageBasedType,
		ScalingRouteO11y2HPAByResourceUsageBasedType:
		return scaler_o11y_res_based_v1.ScalerO11yResBasedV1IdStr
	case ScalingRouteK8s2BlackholeByCPUUsageBasedType,
		ScalingRouteK8s2HPAByCPUUsageBasedType:
		return scaler_k8s_v1.ScalerK8sV1IdStr
	case ScalingRouteTiKVCapacity2ReservationType:
		return scaler_tikv_capacity_v1.ScalerTiKVCapacityV1IdStr
	default:
		return ""
	}
}

func parseComponentScalerConfig(cfg map[string]any, componentScalerType framework.PhasedComponentScalerType) (framework.ConfigData, error) {
	if cfg == nil {
		return nil, nil //nolint:nilnil // nil config is valid and will use default values
	}
	switch componentScalerType {
	case scaler_o11y_res_based_v1.ScalerO11yResBasedV1IdStr:
		return scaler_o11y_res_based_v1.ParseConfig(cfg)
	case scaler_k8s_v1.ScalerK8sV1IdStr:
		return scaler_k8s_v1.ParseConfig(cfg)
	case scaler_tikv_capacity_v1.ScalerTiKVCapacityV1IdStr:
		return scaler_tikv_capacity_v1.ParseConfig(cfg)
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown component scaler type: %s", componentScalerType)
	}
}

func parseScalerDefaultConfig(cfg map[string]any, scalerType string) (framework.ConfigData, error) {
	if cfg == nil {
		return nil, nil //nolint:nilnil // nil config is valid and will be merged with cluster-level config
	}
	content, err := yaml.Marshal(cfg)
	if err != nil {
		return nil, err
	}
	switch scalerType {
	case string(framework.ClusterScalerPhasedComponentScalerV1IdStr),
		string(ScalingRouteK8s2BlackholeByResourceUsageBasedType),
		string(ScalingRouteK8s2HPAByResourceUsageBasedType),
		string(ScalingRouteK8s2BlackholeByCPUUsageBasedType),
		string(ScalingRouteK8s2HPAByCPUUsageBasedType),
		string(ScalingRouteO11y2BlackholeByResourceUsageBasedType),
		string(ScalingRouteO11y2HPAByResourceUsageBasedType),
		string(ScalingRouteTiKVCapacity2ReservationType):
		var raw struct {
			ComponentName         string         `yaml:"componentName"`
			ComponentScalerConfig map[string]any `yaml:"componentScalerConfig"`
		}
		if err := yaml.UnmarshalStrict(content, &raw); err != nil {
			return nil, err
		}
		result := &framework.ClusterScalerPhasedComponentScalerV1Config{
			ComponentName: framework.ComponentName(raw.ComponentName),
		}
		componentScalerType := getComponentScalerType(scalerType)
		if componentScalerType != "" && raw.ComponentScalerConfig != nil {
			componentScalerCfg, err := parseComponentScalerConfig(raw.ComponentScalerConfig, componentScalerType)
			if err != nil {
				return nil, fmt.Errorf("failed to parse componentScalerConfig: %w", err)
			}
			result.ComponentScalerConfig = componentScalerCfg
		}
		return result, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown scaler type: %s", scalerType)
	}
}

func parseScalerConfig(cfg map[string]any, scalerType string) (framework.ConfigData, error) {
	if cfg == nil {
		return nil, nil //nolint:nilnil // nil config is valid and will be merged with default config
	}
	switch scalerType {
	case string(framework.ClusterScalerPhasedComponentScalerV1IdStr),
		string(ScalingRouteK8s2BlackholeByResourceUsageBasedType),
		string(ScalingRouteK8s2HPAByResourceUsageBasedType),
		string(ScalingRouteK8s2BlackholeByCPUUsageBasedType),
		string(ScalingRouteK8s2HPAByCPUUsageBasedType),
		string(ScalingRouteO11y2BlackholeByResourceUsageBasedType),
		string(ScalingRouteO11y2HPAByResourceUsageBasedType),
		string(ScalingRouteTiKVCapacity2ReservationType):
		content, err := yaml.Marshal(cfg)
		if err != nil {
			return nil, err
		}
		var raw struct {
			ComponentName         string         `yaml:"componentName"`
			ComponentScalerConfig map[string]any `yaml:"componentScalerConfig"`
		}
		if err := yaml.UnmarshalStrict(content, &raw); err != nil {
			return nil, err
		}
		result := &framework.ClusterScalerPhasedComponentScalerV1Config{
			ComponentName: framework.ComponentName(raw.ComponentName),
		}
		componentScalerType := getComponentScalerType(scalerType)
		if componentScalerType != "" && raw.ComponentScalerConfig != nil {
			componentScalerCfg, err := parseComponentScalerConfig(raw.ComponentScalerConfig, componentScalerType)
			if err != nil {
				return nil, fmt.Errorf("failed to parse componentScalerConfig: %w", err)
			}
			result.ComponentScalerConfig = componentScalerCfg
		}
		return result, nil
	case string(scaler_o11y_res_based_v1.ScalerO11yResBasedV1IdStr):
		return scaler_o11y_res_based_v1.ParseConfig(cfg)
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown scaler type: %s", scalerType)
	}
}

func parseExecutorConfig(cfg map[string]any, executorName string) (framework.ConfigData, error) {
	if cfg == nil {
		cfg = map[string]any{}
	}
	content, err := yaml.Marshal(cfg)
	if err != nil {
		return nil, err
	}
	switch executorName {
	case string(framework.ScalerInterfaceV1ExecutorSyncLoopIdStr),
		string(framework.ScalerInterfaceV2ExecutorSyncLoopIdStr):
		defaultCfg := framework.NewDefaultScalerInterfaceV1ExecutorSyncLoopConfig()
		var c framework.ScalerInterfaceV1ExecutorSyncLoopConfig
		if err := yaml.UnmarshalStrict(content, &c); err != nil {
			return nil, err
		}
		return c.FillBlankValuesFrom(defaultCfg), nil
	default:
		return nil, bizerr.New(bizerr.ErrCodePanic, "unknown executor name: %s", executorName)
	}
}

func ConvertToInputProviderConfig(cfg InputConfig) (framework.InputProviderConfig, error) {
	config, err := parseInputConfig(cfg.Config, cfg.Type)
	if err != nil {
		return framework.InputProviderConfig{}, err
	}
	return framework.InputProviderConfig{
		UniqName: cfg.UniqName,
		InputProviderMeta: framework.InputProviderMeta{
			Type:             framework.InputProviderType(cfg.Type),
			InterfaceVersion: framework.InputInterfaceVersion(cfg.InterfaceVersion),
		},
		Config: config,
	}, nil
}

func ConvertToOutputConfig(cfg OutputConfig) (framework.OutputConfig, error) {
	config, err := parseOutputConfig(cfg.Config, cfg.Type)
	if err != nil {
		return framework.OutputConfig{}, err
	}
	return framework.OutputConfig{
		UniqName: cfg.UniqName,
		OutputMeta: framework.OutputMeta{
			Type:             framework.OutputType(cfg.Type),
			InterfaceVersion: framework.OutputInterfaceVersion(cfg.InterfaceVersion),
		},
		Config: config,
	}, nil
}

func ConvertToScalerConfig(cfg *ScalerConfig, inputType, outputType string) (*framework.ScalerConfig, error) {
	defaultConfig, err := parseScalerDefaultConfig(cfg.DefaultConfig, cfg.Type)
	if err != nil {
		return nil, err
	}
	var inputConfig framework.ConfigData
	if cfg.InputConfig != nil {
		inputConfig, err = parseInputConfig(cfg.InputConfig, inputType)
		if err != nil {
			return nil, fmt.Errorf("failed to parse inputConfig: %w", err)
		}
	}
	var outputConfig framework.ConfigData
	if cfg.OutputConfig != nil {
		outputConfig, err = parseOutputConfig(cfg.OutputConfig, outputType)
		if err != nil {
			return nil, fmt.Errorf("failed to parse outputConfig: %w", err)
		}
	}
	return &framework.ScalerConfig{
		UniqName: framework.ScalerUniqName(cfg.UniqName),
		ScalerMeta: framework.UnattachedScalerMeta{
			Type:             framework.ClusterScalerType(cfg.Type),
			InterfaceVersion: framework.ClusterScalerInterfaceVersion(cfg.InterfaceVersion),
		},
		InputUniqName:  cfg.InputUniqName,
		OutputUniqName: cfg.OutputUniqName,
		DefaultConfig:  defaultConfig,
		InputConfig:    inputConfig,
		OutputConfig:   outputConfig,
	}, nil
}

func ConvertToClusterScalerConfig(cfg *ClusterScalerConfig, scalerType, inputType, outputType string) (*framework.ClusterScalerConfig, error) {
	scalerConfig, err := parseScalerConfig(cfg.ScalerConfig, scalerType)
	if err != nil {
		return nil, err
	}
	executorConfig, err := parseExecutorConfig(cfg.ExecutorConfig, cfg.ExecutorName)
	if err != nil {
		return nil, err
	}
	var inputConfig framework.ConfigData
	if cfg.InputConfig != nil {
		inputConfig, err = parseInputConfig(cfg.InputConfig, inputType)
		if err != nil {
			return nil, err
		}
	}
	var outputConfig framework.ConfigData
	if cfg.OutputConfig != nil {
		outputConfig, err = parseOutputConfig(cfg.OutputConfig, outputType)
		if err != nil {
			return nil, err
		}
	}
	return &framework.ClusterScalerConfig{
		ScalerUniqName:  framework.ScalerUniqName(cfg.ScalerUniqName),
		TargetClusterId: framework.ClusterId(cfg.TargetClusterId),
		ExecutorName:    framework.ScalingTaskExecutorName(cfg.ExecutorName),
		ScalerConfig:    scalerConfig,
		ExecutorConfig:  executorConfig,
		InputConfig:     inputConfig,
		OutputConfig:    outputConfig,
	}, nil
}

func GetScalerType(scalers []ScalerConfig, scalerUniqName string) (string, bool) {
	for _, s := range scalers {
		if s.UniqName == scalerUniqName {
			return s.Type, true
		}
	}
	return "", false
}
