/*
 * Copyright (c) 2026 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package analyzer

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"sync/atomic"
	"time"

	"many-core-orchestrator/api/v1alpha1"

	olog "gopkg.openfuyao.cn/common-modules/ologger/log"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/yaml"
)

type RuleEngine interface {
	Evaluate(metrics NodeMetrics) []Detection
}

type InterferenceCalculator interface {
	Compute(metrics NodeMetrics) float64
}

type DetectionRules struct {
	Weights RuleWeights `json:"weights"`

	Saturation RuleSaturation `json:"saturation"`
	Thresholds RuleThresholds `json:"thresholds"`
	Penalties  RulePenalties  `json:"penalties"`

	DegradedMemory RuleDegradedMemory `json:"degradedMemory"`
}

type RuleWeights struct {
	Cache  float64 `json:"cache"`
	Memory float64 `json:"memory"`
	IO     float64 `json:"io"`
}

type RuleSaturation struct {
	CacheProduct    float64 `json:"cacheProduct"`
	PSIMemory       float64 `json:"psiMemory"`
	BioLatencyP99Ms float64 `json:"bioLatencyP99Ms"`
	PSIIO           float64 `json:"psiIo"`
}

type RuleThresholds struct {
	CacheMissRate      float64 `json:"cacheMissRate"`
	LLCOccupancy       float64 `json:"llcOccupancy"`
	BioLatencyP99Ms    float64 `json:"bioLatencyP99Ms"`
	PSIIO              float64 `json:"psiIo"`
	PSIMemorySome      float64 `json:"psiMemorySome"`
	PSIMemoryFull      float64 `json:"psiMemoryFull"`
	IOPenaltyP99Ms     float64 `json:"ioPenaltyP99Ms"`
	IOPenaltyPSIIO     float64 `json:"ioPenaltyPsiIo"`
	MemIOPenaltyMemory float64 `json:"memIoPenaltyMemory"`
	MemIOPenaltyPSIIO  float64 `json:"memIoPenaltyPsiIo"`
}

type RulePenalties struct {
	IOMultiplier    float64 `json:"ioMultiplier"`
	MemIOMultiplier float64 `json:"memIoMultiplier"`
}

type RuleDegradedMemory struct {
	MemAvailableRatioThreshold float64 `json:"memAvailableRatioThreshold"`
	PageScanRateThreshold      float64 `json:"pageScanRateThreshold"`
}

func DefaultDetectionRules() DetectionRules {
	return DetectionRules{
		Weights: RuleWeights{
			Cache:  0.30,
			Memory: 0.30,
			IO:     0.40,
		},
		Saturation: RuleSaturation{
			CacheProduct:    0.175,
			PSIMemory:       50,
			BioLatencyP99Ms: 100,
			PSIIO:           20,
		},
		Thresholds: RuleThresholds{
			CacheMissRate:      0.20,
			LLCOccupancy:       0.70,
			BioLatencyP99Ms:    50,
			PSIIO:              20,
			PSIMemorySome:      10,
			PSIMemoryFull:      0,
			IOPenaltyP99Ms:     50,
			IOPenaltyPSIIO:     20,
			MemIOPenaltyMemory: 50,
			MemIOPenaltyPSIIO:  20,
		},
		Penalties: RulePenalties{
			IOMultiplier:    1.2,
			MemIOMultiplier: 1.1,
		},
		DegradedMemory: RuleDegradedMemory{
			MemAvailableRatioThreshold: 0.30,
			PageScanRateThreshold:      10000,
		},
	}
}

type RuleConfigStore struct {
	logger         *olog.Logger
	resource       schema.GroupVersionResource
	namespace      string
	name           string
	current        atomic.Value
	lastHash       atomic.Value
	lastLoadedTime atomic.Value
}

func NewRuleConfigStore(cfg Config, logger *olog.Logger) *RuleConfigStore {
	store := &RuleConfigStore{
		logger: logger,
		resource: schema.GroupVersionResource{
			Group:    cfg.DetectionRulesGroup,
			Version:  cfg.DetectionRulesVersion,
			Resource: cfg.DetectionRulesResource,
		},
		namespace: cfg.DetectionRulesNamespace,
		name:      cfg.DetectionRulesName,
	}
	store.current.Store(DefaultDetectionRules())
	return store
}

func (s *RuleConfigStore) Evaluate(metrics NodeMetrics) []Detection {
	return s.rules().Evaluate(metrics)
}

func (s *RuleConfigStore) Compute(metrics NodeMetrics) float64 {
	return s.rules().Compute(metrics)
}

func (s *RuleConfigStore) rules() DetectionRules {
	rules, ok := s.current.Load().(DetectionRules)
	if !ok {
		return DefaultDetectionRules()
	}
	return rules
}

func (s *RuleConfigStore) Metadata() RuleConfigMetadata {
	hash, _ := s.lastHash.Load().(string)
	loadedAt, _ := s.lastLoadedTime.Load().(time.Time)
	return RuleConfigMetadata{Hash: hash, LastLoadedTime: loadedAt}
}

type RuleConfigMetadata struct {
	Hash           string
	LastLoadedTime time.Time
}

func (s *RuleConfigStore) ApplyDetectionRuleConfig(event string, ruleConfig *v1alpha1.DetectionRuleConfig) error {
	if !s.isTargetRuleConfig(ruleConfig) {
		return nil
	}
	rules, hash, err := s.rulesFromObject(ruleConfig)
	if err != nil {
		ruleReloadTotal.WithLabelValues("error").Inc()
		s.logger.Error("reload detection rules CR failed; keeping previous valid config",
			"error", err,
			"event", event,
			"namespace", s.namespace,
			"name", s.name,
			"resource", s.resource.Resource,
		)
		return err
	}
	s.current.Store(rules)
	loadedAt := time.Now().UTC()
	s.lastLoadedTime.Store(loadedAt)
	ruleReloadTotal.WithLabelValues("success").Inc()
	ruleLastReloadTimestamp.Set(float64(loadedAt.Unix()))
	lastHash, _ := s.lastHash.Load().(string)
	if hash != lastHash {
		if lastHash != "" {
			ruleConfigInfo.DeleteLabelValues(lastHash, s.ruleConfigRef())
		}
		ruleConfigInfo.WithLabelValues(hash, s.ruleConfigRef()).Set(1)
		s.lastHash.Store(hash)
		if lastHash == "" {
			s.logger.Info("loaded detection rules CR",
				"event", event,
				"namespace", s.namespace,
				"name", s.name,
				"resource", s.resource.Resource,
				"hash", hash,
				"loaded_at", loadedAt.Format(time.RFC3339),
			)
		} else {
			s.logger.Info("reloaded detection rules CR",
				"event", event,
				"namespace", s.namespace,
				"name", s.name,
				"resource", s.resource.Resource,
				"hash", hash,
				"previous_hash", lastHash,
				"loaded_at", loadedAt.Format(time.RFC3339),
			)
		}
	}
	return nil
}

func (s *RuleConfigStore) KeepLastValidAfterDelete() {
	s.logger.Warn("detection rules CR deleted; keeping previous valid config",
		"namespace", s.namespace,
		"name", s.name,
		"resource", s.resource.Resource,
	)
}

func (s *RuleConfigStore) rulesFromObject(ruleConfig *v1alpha1.DetectionRuleConfig) (DetectionRules, string, error) {
	if !s.isTargetRuleConfig(ruleConfig) {
		return DetectionRules{}, "", fmt.Errorf("ignore non-target rule config %s/%s", ruleConfig.GetNamespace(), ruleConfig.GetName())
	}
	data, err := json.Marshal(ruleConfig.Spec)
	if err != nil {
		return DetectionRules{}, "", err
	}
	rules, err := LoadDetectionRulesSpec(data)
	if err != nil {
		return DetectionRules{}, "", err
	}
	return rules, hashBytes(data), nil
}

func (s *RuleConfigStore) isTargetRuleConfig(obj *v1alpha1.DetectionRuleConfig) bool {
	return obj.GetNamespace() == s.namespace && obj.GetName() == s.name
}

func LoadDetectionRulesSpec(data []byte) (DetectionRules, error) {
	rules := DefaultDetectionRules()
	if err := json.Unmarshal(data, &rules); err != nil {
		return DetectionRules{}, err
	}
	rules.applyDefaults()
	return rules, nil
}

func (s *RuleConfigStore) ruleConfigRef() string {
	return fmt.Sprintf("%s/%s/%s.%s/%s", s.namespace, s.name, s.resource.Resource, s.resource.Version, s.resource.Group)
}

func LoadDetectionRules(path string) (DetectionRules, string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return DetectionRules{}, "", err
	}
	rules := DefaultDetectionRules()
	if err := yaml.Unmarshal(data, &rules); err != nil {
		return DetectionRules{}, "", err
	}
	rules.applyDefaults()
	return rules, hashBytes(data), nil
}

func hashBytes(data []byte) string {
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func (r *DetectionRules) applyDefaults() {
	defaults := DefaultDetectionRules()
	r.applyWeightDefaults(defaults.Weights)
	r.applySaturationDefaults(defaults.Saturation)
	r.applyThresholdDefaults(defaults.Thresholds)
	r.applyPenaltyDefaults(defaults.Penalties)
	r.applyDegradedMemoryDefaults(defaults.DegradedMemory)
}

func (r *DetectionRules) applyWeightDefaults(defaults RuleWeights) {
	defaultPositive(&r.Weights.Cache, defaults.Cache)
	defaultPositive(&r.Weights.Memory, defaults.Memory)
	defaultPositive(&r.Weights.IO, defaults.IO)
}

func (r *DetectionRules) applySaturationDefaults(defaults RuleSaturation) {
	defaultPositive(&r.Saturation.CacheProduct, defaults.CacheProduct)
	defaultPositive(&r.Saturation.PSIMemory, defaults.PSIMemory)
	defaultPositive(&r.Saturation.BioLatencyP99Ms, defaults.BioLatencyP99Ms)
	defaultPositive(&r.Saturation.PSIIO, defaults.PSIIO)
}

func (r *DetectionRules) applyThresholdDefaults(defaults RuleThresholds) {
	defaultPositive(&r.Thresholds.CacheMissRate, defaults.CacheMissRate)
	defaultPositive(&r.Thresholds.LLCOccupancy, defaults.LLCOccupancy)
	defaultPositive(&r.Thresholds.BioLatencyP99Ms, defaults.BioLatencyP99Ms)
	defaultPositive(&r.Thresholds.PSIIO, defaults.PSIIO)
	defaultPositive(&r.Thresholds.PSIMemorySome, defaults.PSIMemorySome)
	defaultPositive(&r.Thresholds.IOPenaltyP99Ms, defaults.IOPenaltyP99Ms)
	defaultPositive(&r.Thresholds.IOPenaltyPSIIO, defaults.IOPenaltyPSIIO)
	defaultPositive(&r.Thresholds.MemIOPenaltyMemory, defaults.MemIOPenaltyMemory)
	defaultPositive(&r.Thresholds.MemIOPenaltyPSIIO, defaults.MemIOPenaltyPSIIO)
}

func (r *DetectionRules) applyPenaltyDefaults(defaults RulePenalties) {
	defaultPositive(&r.Penalties.IOMultiplier, defaults.IOMultiplier)
	defaultPositive(&r.Penalties.MemIOMultiplier, defaults.MemIOMultiplier)
}

func (r *DetectionRules) applyDegradedMemoryDefaults(defaults RuleDegradedMemory) {
	defaultPositive(&r.DegradedMemory.MemAvailableRatioThreshold, defaults.MemAvailableRatioThreshold)
	defaultPositive(&r.DegradedMemory.PageScanRateThreshold, defaults.PageScanRateThreshold)
}

func defaultPositive(value *float64, fallback float64) {
	if *value <= 0 {
		*value = fallback
	}
}

type DefaultRuleEngine struct{}

func (DefaultRuleEngine) Evaluate(metrics NodeMetrics) []Detection {
	return DefaultDetectionRules().Evaluate(metrics)
}

func (r DetectionRules) Evaluate(metrics NodeMetrics) []Detection {
	var detections []Detection
	if metrics.LLCMissRate > r.Thresholds.CacheMissRate && metrics.LLCOccupancy > r.Thresholds.LLCOccupancy {
		detections = append(detections, Detection{
			Type:    "cache-pollution",
			Message: fmt.Sprintf("LLC miss rate %.2f > %.2f and LLC occupancy %.2f > %.2f", metrics.LLCMissRate, r.Thresholds.CacheMissRate, metrics.LLCOccupancy, r.Thresholds.LLCOccupancy),
		})
	}
	if metrics.BioLatencyP99Ms > r.Thresholds.BioLatencyP99Ms || metrics.PSIIOAvg10 > r.Thresholds.PSIIO {
		if !metrics.PSIAvailable && metrics.BioLatencyP99Ms <= r.Thresholds.BioLatencyP99Ms {
			return detections
		}
		detections = append(detections, Detection{
			Type:    "io-queue-contention",
			Message: fmt.Sprintf("Bio latency P99 %.2fms > %.2fms or PSI I/O some %.2f%% > %.2f%%", metrics.BioLatencyP99Ms, r.Thresholds.BioLatencyP99Ms, metrics.PSIIOAvg10, r.Thresholds.PSIIO),
		})
	}
	if metrics.PSIAvailable && (metrics.PSIMemAvg10 > r.Thresholds.PSIMemorySome || metrics.PSIMemFullAvg10 > r.Thresholds.PSIMemoryFull) {
		detections = append(detections, Detection{
			Type:    "memory-pressure",
			Message: fmt.Sprintf("PSI memory some %.2f%%, full %.2f%%", metrics.PSIMemAvg10, metrics.PSIMemFullAvg10),
		})
	} else if !metrics.PSIAvailable && r.degradedMemoryPressure(metrics) {
		detections = append(detections, Detection{
			Type:    "memory-pressure",
			Message: fmt.Sprintf("PSI unavailable; mem available ratio %.2f, page scan rate %.2f pages/s", metrics.MemAvailRatio, metrics.PageScanRate),
		})
	}
	return detections
}

type WeightedCalculator struct{}

func (WeightedCalculator) Compute(metrics NodeMetrics) float64 {
	return DefaultDetectionRules().Compute(metrics)
}

func (r DetectionRules) Compute(metrics NodeMetrics) float64 {
	cacheIndex := r.cacheIndex(metrics)
	cacheWeight := r.Weights.Cache
	memWeight := r.Weights.Memory
	ioWeight := r.Weights.IO

	if !metrics.PSIAvailable {
		memIndex := r.degradedMemIndex(metrics)
		ioIndex := normalize(metrics.BioLatencyP99Ms, r.Saturation.BioLatencyP99Ms)
		return clamp(cacheWeight*cacheIndex + memWeight*memIndex + ioWeight*ioIndex)
	}

	memIndex := normalize(max(metrics.PSIMemAvg10, metrics.PSIMemFullAvg10), r.Saturation.PSIMemory)
	ioIndex := max(normalize(metrics.BioLatencyP99Ms, r.Saturation.BioLatencyP99Ms), normalize(metrics.PSIIOAvg10, r.Saturation.PSIIO))
	if metrics.PSIIOAvg10 > r.Thresholds.IOPenaltyPSIIO && metrics.BioLatencyP99Ms > r.Thresholds.IOPenaltyP99Ms {
		ioWeight *= r.Penalties.IOMultiplier
	}
	if metrics.PSIMemAvg10 > r.Thresholds.MemIOPenaltyMemory && metrics.PSIIOAvg10 > r.Thresholds.MemIOPenaltyPSIIO {
		memWeight *= r.Penalties.MemIOMultiplier
		ioWeight *= r.Penalties.MemIOMultiplier
	}

	return clamp(cacheWeight*cacheIndex + memWeight*memIndex + ioWeight*ioIndex)
}

func (r DetectionRules) cacheIndex(metrics NodeMetrics) float64 {
	return normalize(metrics.LLCMissRate*metrics.LLCOccupancy, r.Saturation.CacheProduct)
}

func (r DetectionRules) degradedMemIndex(metrics NodeMetrics) float64 {
	availableFactor := normalize(1.0-metrics.MemAvailRatio, r.DegradedMemory.MemAvailableRatioThreshold)
	reclaimFactor := normalize(metrics.PageScanRate, r.DegradedMemory.PageScanRateThreshold)
	return clamp(0.6*availableFactor + 0.4*reclaimFactor)
}

func (r DetectionRules) degradedMemoryPressure(metrics NodeMetrics) bool {
	usedRatio := 1.0 - metrics.MemAvailRatio
	return usedRatio >= r.DegradedMemory.MemAvailableRatioThreshold ||
		metrics.PageScanRate >= r.DegradedMemory.PageScanRateThreshold
}

func normalize(value, saturation float64) float64 {
	if saturation <= 0 {
		return 0
	}
	return clamp(value / saturation)
}

func clamp(value float64) float64 {
	if value < 0 {
		return 0
	}
	if value > 1 {
		return 1
	}
	return value
}

func max(a, b float64) float64 {
	if a > b {
		return a
	}
	return b
}