/*
 * Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package config

import (
	"context"
	"errors"
	"os"
	"strings"

	"github.com/opencord/voltha-lib-go/v7/pkg/log"
)

const (
	defaultTracingStatusKey        = "trace_publish"   // kvstore key containing tracing configuration status
	defaultLogCorrelationStatusKey = "log_correlation" // kvstore key containing log correlation configuration status
)

// ComponentLogFeatureController represents Configuration for Logging related features of Tracing and Log
// Correlation of specific Voltha component.
type ComponentLogFeaturesController struct {
	ComponentName               string
	componentNameConfig         *ComponentConfig
	configManager               *ConfigManager
	initialTracingStatus        bool // Initial default tracing status set by helm chart
	initialLogCorrelationStatus bool // Initial default log correlation status set by helm chart
}

func NewComponentLogFeaturesController(ctx context.Context, cm *ConfigManager) (*ComponentLogFeaturesController, error) {
	logger.Debug(ctx, "creating-new-component-log-features-controller")
	componentName := os.Getenv("COMPONENT_NAME")
	if componentName == "" {
		return nil, errors.New("unable to retrieve PoD Component Name from Runtime env")
	}

	tracingStatus := log.GetGlobalLFM().GetTracePublishingStatus()
	logCorrelationStatus := log.GetGlobalLFM().GetLogCorrelationStatus()

	return &ComponentLogFeaturesController{
		ComponentName:               componentName,
		componentNameConfig:         nil,
		configManager:               cm,
		initialTracingStatus:        tracingStatus,
		initialLogCorrelationStatus: logCorrelationStatus,
	}, nil

}

// StartLogFeaturesConfigProcessing persists initial config of Log Features into Config Store before
// starting the loading and processing of Configuration updates
func StartLogFeaturesConfigProcessing(cm *ConfigManager, ctx context.Context) {
	cc, err := NewComponentLogFeaturesController(ctx, cm)
	if err != nil {
		logger.Errorw(ctx, "unable-to-construct-component-log-features-controller-instance-for-monitoring", log.Fields{"error": err})
		return
	}

	cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogFeatures)
	logger.Debugw(ctx, "component-log-features-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})

	cc.persistInitialLogFeaturesConfigs(ctx)

	cc.processLogFeaturesConfig(ctx)
}

// Method to persist Initial status of Log Correlation and Tracing features (as set from command line)
// into config store (etcd kvstore), if not set yet
func (cc *ComponentLogFeaturesController) persistInitialLogFeaturesConfigs(ctx context.Context) {

	_, err := cc.componentNameConfig.Retrieve(ctx, defaultTracingStatusKey)
	if err != nil {
		statusString := "DISABLED"
		if cc.initialTracingStatus {
			statusString = "ENABLED"
		}
		err = cc.componentNameConfig.Save(ctx, defaultTracingStatusKey, statusString)
		if err != nil {
			logger.Errorw(ctx, "failed-to-persist-component-initial-tracing-status-at-startup", log.Fields{"error": err, "tracingstatus": statusString})
		}
	}

	_, err = cc.componentNameConfig.Retrieve(ctx, defaultLogCorrelationStatusKey)
	if err != nil {
		statusString := "DISABLED"
		if cc.initialLogCorrelationStatus {
			statusString = "ENABLED"
		}
		err = cc.componentNameConfig.Save(ctx, defaultLogCorrelationStatusKey, statusString)
		if err != nil {
			logger.Errorw(ctx, "failed-to-persist-component-initial-log-correlation-status-at-startup", log.Fields{"error": err, "logcorrelationstatus": statusString})
		}
	}
}

// processLogFeaturesConfig will first load and apply configuration of log features. Then it will start waiting for any changes
// made to configuration in config store (etcd) and apply the same
func (cc *ComponentLogFeaturesController) processLogFeaturesConfig(ctx context.Context) {

	// Load and apply Tracing Status and log correlation status for first time
	cc.loadAndApplyTracingStatusUpdate(ctx)
	cc.loadAndApplyLogCorrelationStatusUpdate(ctx)

	componentConfigEventChan := cc.componentNameConfig.MonitorForConfigChange(ctx)

	// process the change events received on the channel
	var configEvent *ConfigChangeEvent
	for {
		select {
		case <-ctx.Done():
			return

		case configEvent = <-componentConfigEventChan:
			logger.Debugw(ctx, "processing-log-features-config-change", log.Fields{"ChangeType": configEvent.ChangeType, "Package": configEvent.ConfigAttribute})

			if strings.HasSuffix(configEvent.ConfigAttribute, defaultTracingStatusKey) {
				cc.loadAndApplyTracingStatusUpdate(ctx)
			} else if strings.HasSuffix(configEvent.ConfigAttribute, defaultLogCorrelationStatusKey) {
				cc.loadAndApplyLogCorrelationStatusUpdate(ctx)
			}
		}
	}

}

func (cc *ComponentLogFeaturesController) loadAndApplyTracingStatusUpdate(ctx context.Context) {

	desiredTracingStatus, err := cc.componentNameConfig.Retrieve(ctx, defaultTracingStatusKey)
	if err != nil || desiredTracingStatus == "" {
		logger.Warn(ctx, "unable-to-retrieve-tracing-status-from-config-store")
		return
	}

	if desiredTracingStatus != "ENABLED" && desiredTracingStatus != "DISABLED" {
		logger.Warnw(ctx, "unsupported-tracing-status-configured-in-config-store", log.Fields{"failed-tracing-status": desiredTracingStatus, "tracing-status": log.GetGlobalLFM().GetTracePublishingStatus()})
		return
	}

	logger.Debugw(ctx, "retrieved-tracing-status", log.Fields{"tracing-status": desiredTracingStatus})

	log.GetGlobalLFM().SetTracePublishingStatus(desiredTracingStatus == "ENABLED")
}

func (cc *ComponentLogFeaturesController) loadAndApplyLogCorrelationStatusUpdate(ctx context.Context) {

	desiredLogCorrelationStatus, err := cc.componentNameConfig.Retrieve(ctx, defaultLogCorrelationStatusKey)
	if err != nil || desiredLogCorrelationStatus == "" {
		logger.Warn(ctx, "unable-to-retrieve-log-correlation-status-from-config-store")
		return
	}

	if desiredLogCorrelationStatus != "ENABLED" && desiredLogCorrelationStatus != "DISABLED" {
		logger.Warnw(ctx, "unsupported-log-correlation-status-configured-in-config-store", log.Fields{"failed-log-correlation-status": desiredLogCorrelationStatus, "log-correlation-status": log.GetGlobalLFM().GetLogCorrelationStatus()})
		return
	}

	logger.Debugw(ctx, "retrieved-log-correlation-status", log.Fields{"log-correlation-status": desiredLogCorrelationStatus})

	log.GetGlobalLFM().SetLogCorrelationStatus(desiredLogCorrelationStatus == "ENABLED")
}
