package framework

import (
	"context"
	"fmt"
	"sync"
)

type ClusterScalerExecutor struct {
	eventReceiver              EventReceiver
	inputConfigs               *InputProviderConfigManager
	outputConfigs              *OutputConfigManager
	scalerConfigs              *ScalerConfigManager
	clusterScalerConfigs       *ClusterScalerConfigManager
	clusterScalerManager       *ClusterScalerManager
	scalingTaskExecutorManager *ScalingTaskExecutorManager
	errCh                      chan error
}

func NewClusterScalerExecutor(eventReceiver EventReceiver) (*ClusterScalerExecutor, error) {
	clusterScalerManager, err := NewDefaultClusterScalerManager(eventReceiver)
	if err != nil {
		return nil, err
	}
	scalingTaskExecutorManager, err := NewDefaultScalingTaskExecutorManager(eventReceiver)
	if err != nil {
		return nil, err
	}
	executor := &ClusterScalerExecutor{
		eventReceiver:              eventReceiver,
		inputConfigs:               NewInputProviderConfigManager(eventReceiver),
		outputConfigs:              NewOutputConfigManager(eventReceiver),
		scalerConfigs:              NewScalerConfigManager(eventReceiver),
		clusterScalerConfigs:       NewClusterScalerConfigManager(eventReceiver),
		clusterScalerManager:       clusterScalerManager,
		scalingTaskExecutorManager: scalingTaskExecutorManager,
	}
	return executor, nil
}

func (this *ClusterScalerExecutor) Errors() <-chan error {
	return this.errCh
}

func (this *ClusterScalerExecutor) checkConfigRef() error {
	// TODO: impl
	// 1. print all un-used input/output/scaler configs
	// 2. make sure all UniqNames in ClusterScalerConfigs are exist
	return nil
}

func (this *ClusterScalerExecutor) Run(waitForFinish bool) error {
	err := this.checkConfigRef()
	if err != nil {
		return err
	}
	this.errCh = make(chan error, 1)
	return this.runAllClusterScalers(waitForFinish)
}

func (this *ClusterScalerExecutor) runAllClusterScalers(waitForFinish bool) error {
	var scalingTaskExecutor ScalingTaskExecutor
	var err error
	var wg sync.WaitGroup
	this.clusterScalerConfigs.Iterate(func(clusterScalerConfig *ClusterScalerConfig) bool {
		scalingTaskExecutor, err = this.CreateScalingExecutor(clusterScalerConfig)
		if err != nil {
			return false
		}
		wg.Add(1)
		go func(executor ScalingTaskExecutor, uniqName string) {
			defer wg.Done()
			// Use background context for static configs — these run until process exit.
			if runErr := executor.Run(context.Background()); runErr != nil {
				this.eventReceiver.OnEvent(
					EventClusterScalerExecutorRunScalingTaskExecutorError,
					EventDataWithName("ExecutorConfig", CloneableString(uniqName)),
					EventDataWithType(EventDataError, NewCloneableError(runErr)),
				)
				select {
				case this.errCh <- runErr:
				default:
				}
			}
		}(scalingTaskExecutor, clusterScalerConfig.UniqName())
		this.eventReceiver.OnEvent(
			EventClusterScalerExecutorRunScalingTaskExecutorOk,
			EventDataWithName("ExecutorConfig", CloneableString(clusterScalerConfig.UniqName())))
		return true
	})
	if waitForFinish {
		wg.Wait()
		close(this.errCh)
	} else {
		go func() {
			wg.Wait()
			close(this.errCh)
		}()
	}
	return err
}

// RunOne creates a ScalingTaskExecutor for the given config and starts it in
// a new goroutine. The returned cancel function stops the goroutine.
// This enables dynamic addition of scalers after Run() has been called.
func (this *ClusterScalerExecutor) RunOne(config *ClusterScalerConfig) (cancel context.CancelFunc, done <-chan struct{}, err error) {
	executor, err := this.CreateScalingExecutor(config)
	if err != nil {
		return nil, nil, err
	}
	ctx, cancelFn := context.WithCancel(context.Background())
	d := make(chan struct{})
	go func() {
		defer close(d)
		if runErr := executor.Run(ctx); runErr != nil && ctx.Err() == nil {
			this.eventReceiver.OnEvent(
				EventClusterScalerExecutorRunScalingTaskExecutorError,
				EventDataWithName("ExecutorConfig", CloneableString(config.UniqName())),
				EventDataWithType(EventDataError, NewCloneableError(runErr)),
			)
		}
	}()
	this.eventReceiver.OnEvent(
		EventClusterScalerExecutorRunScalingTaskExecutorOk,
		EventDataWithName("ExecutorConfig", CloneableString(config.UniqName())))
	return cancelFn, d, nil
}

// CreateScalingExecutor assembles a ScalingTaskExecutor from the given
// ClusterScalerConfig by resolving input/output/scaler configs and creating
// the corresponding instances.
func (this *ClusterScalerExecutor) CreateScalingExecutor(
	clusterScalerConfig *ClusterScalerConfig) (ScalingTaskExecutor, error) {

	scalerConfig, ok := this.scalerConfigs.Get(clusterScalerConfig.ScalerUniqName)
	if !ok {
		return nil, fmt.Errorf("scaler `%s` needed by `%s` but not found",
			clusterScalerConfig.ScalerUniqName, clusterScalerConfig.UniqName())
	}

	inputConfig, ok := this.inputConfigs.Get(scalerConfig.InputUniqName)
	if !ok {
		return nil, fmt.Errorf("input `%s` needed by `%s` but not found",
			scalerConfig.InputUniqName, clusterScalerConfig.ScalerUniqName)
	}

	outputConfig, ok := this.outputConfigs.Get(scalerConfig.OutputUniqName)
	if !ok {
		return nil, fmt.Errorf("output `%s` needed by `%s` but not found",
			scalerConfig.OutputUniqName, clusterScalerConfig.ScalerUniqName)
	}

	clusterScalerConfigData := MergeConfigData(
		clusterScalerConfig.ScalerConfig,
		scalerConfig.DefaultConfig)

	inputConfigData := MergeConfigData(
		clusterScalerConfig.InputConfig,
		MergeConfigData(scalerConfig.InputConfig, inputConfig.Config))

	outputConfigData := MergeConfigData(
		clusterScalerConfig.OutputConfig,
		MergeConfigData(scalerConfig.OutputConfig, outputConfig.Config))

	clusterScalerMeta := &ClusterScalerMeta{
		ScalerMeta: scalerConfig.ScalerMeta,
		InputMeta:  inputConfig.InputProviderMeta,
		OutputMeta: outputConfig.OutputMeta,
	}
	scaler, input, output, err := this.clusterScalerManager.NewClusterScaler(
		clusterScalerMeta,
		clusterScalerConfig.TargetClusterId,
		clusterScalerConfigData,
		inputConfigData,
		outputConfigData)
	if err != nil {
		return nil, err
	}

	executorMeta := &ScalingTaskExecutorMeta{
		ScalerType:             clusterScalerMeta.ScalerMeta.Type,
		ScalerInterfaceVersion: clusterScalerMeta.ScalerMeta.InterfaceVersion,
		InputInterfaceVersion:  clusterScalerMeta.InputMeta.InterfaceVersion,
		OutputInterfaceVersion: clusterScalerMeta.OutputMeta.InterfaceVersion,
		ExecutorName:           clusterScalerConfig.ExecutorName,
	}
	return this.scalingTaskExecutorManager.NewExecutor(
		executorMeta,
		clusterScalerConfig.TargetClusterId,
		scaler,
		input,
		output,
		clusterScalerConfig.ExecutorConfig)
}

func (this *ClusterScalerExecutor) LoadMockConfig() {
	this.inputConfigs.LoadMockConfig()
	this.outputConfigs.LoadMockConfig()
	this.scalerConfigs.LoadMockConfig()
	this.clusterScalerConfigs.LoadMockConfig()
}

func (this *ClusterScalerExecutor) AddInputConfig(config InputProviderConfig) {
	this.inputConfigs.Add(config, true)
}

func (this *ClusterScalerExecutor) AddOutputConfig(config OutputConfig) {
	this.outputConfigs.Add(config, true)
}

func (this *ClusterScalerExecutor) AddScalerConfig(config *ScalerConfig) {
	this.scalerConfigs.Add(config, true)
}

func (this *ClusterScalerExecutor) AddClusterScalerConfig(config *ClusterScalerConfig) {
	this.clusterScalerConfigs.Add(config, true)
}

func (this *ClusterScalerExecutor) GetClusterScalerManager() *ClusterScalerManager {
	return this.clusterScalerManager
}

func (this *ClusterScalerExecutor) GetScalingTaskExecutorManager() *ScalingTaskExecutorManager {
	return this.scalingTaskExecutorManager
}

// TODO: impl with args
func (this *ClusterScalerExecutor) LoadConfigFromFile() error {
	err := this.inputConfigs.LoadFromFile()
	if err != nil {
		return err
	}
	err = this.outputConfigs.LoadFromFile()
	if err != nil {
		return err
	}
	err = this.scalerConfigs.LoadFromFile()
	if err != nil {
		return err
	}
	return this.clusterScalerConfigs.LoadFromFile()
}
