// This file verifies point-in-time case validation, profiles, and committed metadata.
package evaluation

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestDecodeCommittedO11YCasesAreTiDBOnly(t *testing.T) {
	paths, err := filepath.Glob(filepath.Join("cases", "*", PointInTimeCasesDirectory, "*.yaml"))
	if err != nil {
		t.Fatalf("glob committed cases: %v", err)
	}
	if len(paths) != 112 {
		t.Fatalf("committed O11Y cases = %d, want 112", len(paths))
	}

	classes := make(map[OracleClass]int)
	caseIDs := make(map[string]struct{}, len(paths))
	for _, path := range paths {
		file, openErr := os.Open(path)
		if openErr != nil {
			t.Fatalf("open %s: %v", path, openErr)
		}
		spec, decodeErr := DecodeCaseSpec(file)
		closeErr := file.Close()
		if decodeErr != nil || closeErr != nil {
			t.Fatalf("decode/close %s: %v / %v", path, decodeErr, closeErr)
		}

		if spec.Component != "tidb" || spec.Metrics.Type != DataSourceO11Y {
			t.Errorf("case %s = component %q, metrics %q; want TiDB O11Y", path, spec.Component, spec.Metrics.Type)
		}
		if componentDir := filepath.Base(filepath.Dir(filepath.Dir(path))); componentDir != spec.Component {
			t.Errorf("case %s is under component directory %q, want %q", path, componentDir, spec.Component)
		}
		if _, duplicate := caseIDs[spec.ID]; duplicate {
			t.Errorf("case %s duplicates id %q", path, spec.ID)
		}
		caseIDs[spec.ID] = struct{}{}
		classes[spec.Oracle.Class]++

		if spec.Baseline.Replicas <= spec.Current.Replicas {
			t.Errorf("case %s baseline replicas = %d, want more than current %d", path, spec.Baseline.Replicas, spec.Current.Replicas)
		}
		switch spec.Oracle.Class {
		case OracleClassUsefulScaleOut:
			if spec.Oracle.Expected.Replicas != spec.Baseline.Replicas {
				t.Errorf("case %s expected replicas = %d, want historical target %d", path, spec.Oracle.Expected.Replicas, spec.Baseline.Replicas)
			}
		case OracleClassUnnecessaryScaleOut:
			if spec.Oracle.Expected.Replicas != spec.Current.Replicas {
				t.Errorf("case %s expected replicas = %d, want current %d", path, spec.Oracle.Expected.Replicas, spec.Current.Replicas)
			}
		}
		if len(spec.Metrics.Series) != 0 {
			t.Errorf("case %s embeds %d metric series, want request parameters only", path, len(spec.Metrics.Series))
		}
		if spec.Metrics.Profile != "tidb/horizontal-v1" {
			t.Errorf("case %s profile = %q, want tidb/horizontal-v1", path, spec.Metrics.Profile)
		}
		if len(spec.Metrics.Endpoints) != 1 || spec.Metrics.Endpoints[O11YMetricSourceApplication] == "" {
			t.Errorf("case %s endpoints = %+v, want one application endpoint", path, spec.Metrics.Endpoints)
		}
		if spec.Metrics.Window.End != spec.DecisionAt {
			t.Errorf("case %s query window ends at %s, want decisionAt %s", path, spec.Metrics.Window.End, spec.DecisionAt)
		}

		profile, profileErr := LoadMetricsProfile("cases", spec.Metrics.Profile)
		if profileErr != nil {
			t.Fatalf("load profile for %s: %v", path, profileErr)
		}
		exportSpec, buildErr := BuildO11YExportSpec(spec, profile)
		if buildErr != nil {
			t.Fatalf("build export spec for %s: %v", path, buildErr)
		}
		if len(exportSpec.Sources) != 1 || exportSpec.Sources[0].Type != O11YMetricSourceApplication {
			t.Errorf("case %s sources = %+v, want one application source", path, exportSpec.Sources)
			continue
		}
		if len(exportSpec.Sources[0].Queries) != 8 || exportSpec.Sources[0].Queries[0].KeepLabels == nil {
			t.Errorf("case %s resolved queries = %+v, want eight queries with explicit label allowlists", path, exportSpec.Sources[0].Queries)
		}
		for _, query := range exportSpec.Sources[0].Queries {
			if !strings.Contains(query.PromQL, spec.Metrics.Target.ClusterID) {
				t.Errorf("case %s query %q does not select cluster %s", path, query.Name, spec.Metrics.Target.ClusterID)
			}
			if strings.Contains(query.PromQL, "${") {
				t.Errorf("case %s query %q retains an unresolved placeholder", path, query.Name)
			}
		}
	}

	if got := classes[OracleClassUnnecessaryScaleOut]; got != 81 {
		t.Errorf("UnnecessaryScaleOut cases = %d, want 81", got)
	}
	if got := classes[OracleClassUsefulScaleOut]; got != 31 {
		t.Errorf("UsefulScaleOut cases = %d, want 31", got)
	}
}

func TestDecodeCaseSpecMaterializesManualMetrics(t *testing.T) {
	const input = `
version: v1alpha1
id: manual-tidb-case
component: tidb
decisionAt: "2026-06-13T13:34:30Z"
current:
  replicas: 3
baseline:
  replicas: 4
oracle:
  class: UnnecessaryScaleOut
  expected:
    replicas: 3
metrics:
  type: manual
  series:
    - name: tidb_server_connections
      samples:
        - timestamp: "2026-06-13T13:34:00Z"
          value: 10
`

	spec, err := DecodeCaseSpec(strings.NewReader(input))
	if err != nil {
		t.Fatalf("DecodeCaseSpec(): %v", err)
	}
	bundle, err := BuildManualCaseBundle(spec)
	if err != nil {
		t.Fatalf("BuildManualCaseBundle(): %v", err)
	}
	if bundle.Source != DataSourceManual || len(bundle.Input.Metrics) != 1 {
		t.Fatalf("bundle = %+v, want one embedded manual series", bundle)
	}
}

func TestDecodeCaseSpecRejectsUnknownField(t *testing.T) {
	const input = `
version: v1alpha1
id: invalid
component: tidb
decisionAt: "2026-06-13T13:34:30Z"
current:
  replicas: 3
unexpected: true
baseline:
  replicas: 4
oracle:
  class: UnnecessaryScaleOut
  expected:
    replicas: 3
metrics:
  type: manual
  series:
    - name: tidb_server_connections
      samples:
        - timestamp: "2026-06-13T13:34:00Z"
          value: 10
`

	_, err := DecodeCaseSpec(strings.NewReader(input))
	if err == nil || !strings.Contains(err.Error(), "unknown field") {
		t.Fatalf("DecodeCaseSpec() error = %v, want unknown field error", err)
	}
}

func TestBuildO11YExportSpecRejectsEmbeddedMetrics(t *testing.T) {
	decisionAt := time.Date(2026, 6, 13, 13, 34, 30, 0, time.UTC)
	spec := &CaseSpec{
		Version:    CaseBundleVersionV1Alpha1,
		ID:         "invalid-o11y-case",
		Component:  "tidb",
		DecisionAt: decisionAt,
		Current:    Capacity{Replicas: 3},
		Baseline:   Capacity{Replicas: 4},
		Oracle:     Oracle{Class: OracleClassUnnecessaryScaleOut, Expected: Capacity{Replicas: 3}},
		Metrics: CaseMetrics{
			Type:   DataSourceO11Y,
			Series: []MetricSeries{{Name: "embedded"}},
		},
	}

	_, err := BuildO11YExportSpec(spec, nil)
	if err == nil || !strings.Contains(err.Error(), "cannot embed") {
		t.Fatalf("BuildO11YExportSpec() error = %v, want embedded metrics error", err)
	}
}

func TestBuildO11YExportSpecRendersMetricsProfile(t *testing.T) {
	decisionAt := time.Date(2026, 6, 13, 13, 34, 30, 0, time.UTC)
	spec := &CaseSpec{
		Version:    CaseBundleVersionV1Alpha1,
		ID:         "historical-tidb-case",
		Component:  "tidb",
		DecisionAt: decisionAt,
		Current:    Capacity{Replicas: 3},
		Baseline:   Capacity{Replicas: 4},
		Oracle:     Oracle{Class: OracleClassUnnecessaryScaleOut, Expected: Capacity{Replicas: 3}},
		Metrics: CaseMetrics{
			Type:    DataSourceO11Y,
			Profile: "tidb/horizontal-v1",
			Target: &O11YTargetSpec{
				ClusterID: "10122300695965820476",
				Vendor:    "aws",
				Region:    "ap-east-1",
			},
			Window: &O11YQueryWindow{Start: decisionAt.Add(-15 * time.Minute), End: decisionAt, StepSeconds: 60},
			Endpoints: map[O11YMetricSource]string{
				O11YMetricSourceApplication: "https://metrics.example.com/api/v1",
			},
		},
	}
	profile := &MetricsProfile{
		Version:   MetricsProfileVersionV1Alpha1,
		ID:        "tidb/horizontal-v1",
		Component: "tidb",
		Sources: []MetricsProfileSource{{
			Type: O11YMetricSourceApplication,
			Queries: []O11YQuerySpec{{
				Name:       "tidb_connections",
				PromQL:     `tidb_server_connections{tidb_cluster_id="${cluster_id}"}`,
				Type:       O11YQueryTypeRange,
				Required:   true,
				KeepLabels: []string{"instance"},
			}},
		}},
	}

	exportSpec, err := BuildO11YExportSpec(spec, profile)
	if err != nil {
		t.Fatalf("BuildO11YExportSpec(): %v", err)
	}
	if got := exportSpec.Sources[0].BaseURL; got != spec.Metrics.Endpoints[O11YMetricSourceApplication] {
		t.Errorf("base URL = %q, want case endpoint", got)
	}
	if got := exportSpec.Sources[0].Queries[0].PromQL; got != `tidb_server_connections{tidb_cluster_id="10122300695965820476"}` {
		t.Errorf("PromQL = %q, want rendered cluster id", got)
	}
}
