/*
 * 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 (
	"math"
	"testing"
	"time"
)

type testRuleEngine struct{}

func (testRuleEngine) Evaluate(metrics NodeMetrics) []Detection {
	if metrics.LLCMissRate > 0.15 {
		return []Detection{{Type: "cache-pollution", Message: "cache"}}
	}
	return nil
}

type testCalculator struct{}

func (testCalculator) Compute(metrics NodeMetrics) float64 {
	return metrics.LLCMissRate + metrics.LLCOccupancy
}

func TestNodeStateStoreSmoothsSamplesAndConsumesDirtyReport(t *testing.T) {
	store := NewNodeStateStore(10*time.Second, time.Minute, testRuleEngine{}, testCalculator{})
	base := time.Now().UTC()

	store.Add("node-b", base, NodeMetrics{LLCMissRate: 0.10, LLCOccupancy: 0.20, PSIAvailable: true}, "ok")
	report := store.AddWithMetadata("node-b", base.Add(time.Second), NodeMetrics{
		LLCMissRate:   0.30,
		LLCOccupancy:  0.40,
		PSIAvailable:  false,
		PSIIOAvg10:    2,
		PSIMemAvg10:   4,
		MemAvailRatio: 0.8,
		PageScanRate:  10,
	}, "partial", ReportMetadata{
		Sequence:     7,
		Availability: []MetricAvailability{{Collector: "psi", Metric: "psiIoSome", Available: true}},
	})

	if report.SampleCount != 2 {
		t.Fatalf("sample count=%d, want 2", report.SampleCount)
	}
	if report.Metrics.LLCMissRate != 0.20 || math.Abs(report.Metrics.LLCOccupancy-0.30) > 0.000001 {
		t.Fatalf("unexpected smoothed metrics: %+v", report.Metrics)
	}
	if report.PSIAvailable {
		t.Fatalf("PSIAvailable should use latest sample")
	}
	if math.Abs(report.InterferenceLevel-0.50) > 0.000001 {
		t.Fatalf("interference level=%f, want 0.5", report.InterferenceLevel)
	}
	if len(report.Detections) != 1 || report.Detections[0].Type != "cache-pollution" {
		t.Fatalf("detections=%v", report.Detections)
	}
	if report.Sequence != 7 || len(report.Availability) != 1 {
		t.Fatalf("metadata not preserved: %+v", report)
	}

	cluster, ok := store.ConsumeDirtyClusterReportWithRuleConfig(RuleConfigMetadata{
		Hash:           "abc",
		LastLoadedTime: base,
	})
	if !ok {
		t.Fatal("expected dirty report")
	}
	if cluster.RuleConfigHash != "abc" || !cluster.RuleConfigLastLoadedTime.Equal(base) {
		t.Fatalf("rule metadata not attached: %+v", cluster)
	}
	if _, ok := cluster.Nodes["node-b"]; !ok {
		t.Fatalf("cluster nodes=%v", cluster.Nodes)
	}
	if _, ok := store.ConsumeDirtyClusterReport(); ok {
		t.Fatal("dirty flag should be cleared after consume")
	}
}

func TestNodeStateStoreDropsExpiredReportsAndSorts(t *testing.T) {
	store := NewNodeStateStore(10*time.Second, 5*time.Second, testRuleEngine{}, testCalculator{})
	old := time.Now().Add(-time.Minute)
	now := time.Now()
	store.Add("node-z", old, NodeMetrics{LLCMissRate: 0.1}, "ok")
	store.Add("node-a", now, NodeMetrics{LLCMissRate: 0.1}, "ok")

	cluster := store.ClusterReport()
	if _, ok := cluster.Nodes["node-z"]; ok {
		t.Fatalf("expired report should be omitted: %v", cluster.Nodes)
	}
	if _, ok := cluster.Nodes["node-a"]; !ok {
		t.Fatalf("fresh report missing: %v", cluster.Nodes)
	}

	reports := store.Reports()
	if len(reports) != 1 || reports[0].NodeName != "node-a" {
		t.Fatalf("reports=%v", reports)
	}
}

func TestAverageMetricsEmpty(t *testing.T) {
	if got := averageMetrics(nil); got != (NodeMetrics{}) {
		t.Fatalf("averageMetrics(nil)=%+v", got)
	}
}