// This file verifies that evaluation imports and calls the production Policy interface directly.
package evaluation

import (
	"context"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/api/resource"
	"sigs.k8s.io/controller-runtime/pkg/client"

	decisionmetrics "github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/pkg/metrics"
	productionpolicy "github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/pkg/policy"
	"github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/pkg/tier"
)

type testTier struct {
	name string
}

var _ tier.Tier = (*testTier)(nil)

func (t *testTier) Name() string {
	return t.name
}

func (*testTier) Resources() []tier.Resource {
	return nil
}

func (*testTier) Capacity(string) (resource.Quantity, bool) {
	return resource.Quantity{}, false
}

func (*testTier) Patch() client.Patch {
	return nil
}

func (*testTier) Cost() int64 {
	return 0
}

type recordingPolicy struct {
	metrics  []decisionmetrics.Metrics
	tier     tier.Tier
	replicas int32
	calls    int
}

var _ productionpolicy.Policy = (*recordingPolicy)(nil)

func (p *recordingPolicy) Choose(metrics []decisionmetrics.Metrics) (tier.Tier, int32, error) {
	p.calls++
	p.metrics = metrics
	return p.tier, p.replicas, nil
}

func TestEvaluateUsesProductionPolicyContract(t *testing.T) {
	bundle := testTiDBCaseBundle()
	bundle.Input.Current = Capacity{Tier: "S1", Replicas: 3}
	bundle.Input.Metrics = []MetricSeries{
		{
			Name:   "cpu",
			Labels: map[string]string{"instance": "tidb-0"},
			Samples: []MetricSample{
				{Timestamp: bundle.Input.DecisionAt.Add(-time.Minute), Value: 0.4},
				{Timestamp: bundle.Input.DecisionAt, Value: 0.5},
			},
		},
		{
			Name:   "cpu",
			Labels: map[string]string{"instance": "tidb-1"},
			Samples: []MetricSample{
				{Timestamp: bundle.Input.DecisionAt.Add(-time.Minute), Value: 0.6},
				{Timestamp: bundle.Input.DecisionAt, Value: 0.75},
			},
		},
	}
	bundle.Oracle.Expected = Capacity{Tier: "S2", Replicas: 2}
	candidate := &recordingPolicy{tier: &testTier{name: "S2"}, replicas: 2}

	result, err := Evaluate(context.Background(), bundle, candidate)
	if err != nil {
		t.Fatalf("Evaluate(): %v", err)
	}
	if candidate.calls != 1 {
		t.Fatalf("Choose() calls = %d, want 1", candidate.calls)
	}
	if len(candidate.metrics) != 1 {
		t.Fatalf("policy metrics = %+v, want one cpu metric", candidate.metrics)
	}
	series := candidate.metrics[0].Series
	if len(series) != 2 || series[0].Ref.Name != "tidb-0" || series[1].Ref.Name != "tidb-1" ||
		series[0].Samples[0].Value != 0.5 || series[1].Samples[0].Value != 0.75 {
		t.Fatalf("policy metric series = %+v, want latest per-instance samples", series)
	}
	if result.Verdict != VerdictPass || result.Actual != bundle.Oracle.Expected {
		t.Fatalf("result = %+v, want exact desired capacity match", result)
	}
}

func TestEvaluateIgnoresUnspecifiedExpectedTier(t *testing.T) {
	bundle := testTiDBCaseBundle()
	bundle.Input.Current = Capacity{Replicas: 3}
	bundle.Oracle.Expected = Capacity{Replicas: 3}
	candidate := &recordingPolicy{tier: &testTier{name: "S1"}, replicas: 3}

	result, err := Evaluate(context.Background(), bundle, candidate)
	if err != nil {
		t.Fatalf("Evaluate(): %v", err)
	}
	if result.Verdict != VerdictPass {
		t.Fatalf("result = %+v, want omitted expected tier ignored", result)
	}
}

func TestEvaluateWithProjectionCallsProductionPolicyInMappingOrder(t *testing.T) {
	bundle := testTiDBCaseBundle()
	bundle.Input.Metrics = []MetricSeries{
		{Name: "tidb_cpu", Labels: map[string]string{"instance": "a"}, Samples: []MetricSample{{Timestamp: bundle.Input.DecisionAt, Value: 1.5}}},
		{Name: "tidb_memory", Labels: map[string]string{"instance": "a"}, Samples: []MetricSample{{Timestamp: bundle.Input.DecisionAt, Value: 1024}}},
		{Name: "tidb_connections", Labels: map[string]string{"instance": "a"}, Samples: []MetricSample{{Timestamp: bundle.Input.DecisionAt, Value: 10}}},
	}
	candidate := &recordingPolicy{tier: &testTier{name: "S1"}, replicas: bundle.Oracle.Expected.Replicas}

	_, err := EvaluateWithProjection(context.Background(), bundle, candidate, []PolicyMetricMapping{
		{SourceMetric: "tidb_cpu", PolicyMetric: "cpu"},
		{SourceMetric: "tidb_memory", PolicyMetric: "memory"},
	})
	if err != nil {
		t.Fatalf("EvaluateWithProjection(): %v", err)
	}
	if len(candidate.metrics) != 2 {
		t.Fatalf("policy metrics = %+v, want projected CPU and memory only", candidate.metrics)
	}
	if candidate.metrics[0].Series[0].Samples[0].Value != 1.5 || candidate.metrics[1].Series[0].Samples[0].Value != 1024 {
		t.Fatalf("projected values = %+v, want production metric values in mapping order", candidate.metrics)
	}
}
