// This file parses reviewable case YAML and materializes manual or O11Y requests.
package evaluation

import (
	"fmt"
	"io"
	"strings"
	"time"

	"sigs.k8s.io/yaml"
)

const maxCaseSpecBytes = 1 << 20

// CaseAnnotation records review evidence that is never exposed to a policy.
type CaseAnnotation struct {
	LabelingRule  string   `json:"labelingRule,omitempty"`
	Evidence      []string `json:"evidence,omitempty"`
	ReviewStatus  string   `json:"reviewStatus,omitempty"`
	TriggerReason string   `json:"triggerReason,omitempty"`
	Observation   string   `json:"observation,omitempty"`
}

// CaseMetrics selects either embedded manual samples or a fixed O11Y request.
type CaseMetrics struct {
	Type      DataSource                  `json:"type"`
	Series    []MetricSeries              `json:"series,omitempty"`
	Profile   string                      `json:"profile,omitempty"`
	Target    *O11YTargetSpec             `json:"target,omitempty"`
	Window    *O11YQueryWindow            `json:"window,omitempty"`
	Endpoints map[O11YMetricSource]string `json:"endpoints,omitempty"`
}

// CaseSpec is the small, reviewable YAML contract committed to the repository.
// Fetched O11Y samples are materialized into a private CaseBundle at runtime.
type CaseSpec struct {
	Version    string         `json:"version"`
	ID         string         `json:"id"`
	Component  string         `json:"component"`
	DecisionAt time.Time      `json:"decisionAt"`
	Current    Capacity       `json:"current"`
	Baseline   Capacity       `json:"baseline"`
	Oracle     Oracle         `json:"oracle"`
	Annotation CaseAnnotation `json:"annotation,omitempty"`
	Metrics    CaseMetrics    `json:"metrics"`
}

// DecodeCaseSpec decodes one strict YAML case definition.
func DecodeCaseSpec(r io.Reader) (*CaseSpec, error) {
	data, err := io.ReadAll(io.LimitReader(r, maxCaseSpecBytes+1))
	if err != nil {
		return nil, fmt.Errorf("evaluation: read case spec: %w", err)
	}
	if len(data) > maxCaseSpecBytes {
		return nil, fmt.Errorf("evaluation: case spec exceeds %d bytes", maxCaseSpecBytes)
	}

	var spec CaseSpec
	if err := yaml.UnmarshalStrict(data, &spec); err != nil {
		return nil, fmt.Errorf("evaluation: decode case spec: %w", err)
	}
	if err := validateCaseSpec(&spec); err != nil {
		return nil, err
	}
	return &spec, nil
}

// BuildManualCaseBundle materializes embedded samples without external calls.
func BuildManualCaseBundle(spec *CaseSpec) (*CaseBundle, error) {
	if err := validateCaseSpecCommon(spec); err != nil {
		return nil, err
	}
	if spec.Metrics.Type != DataSourceManual {
		return nil, fmt.Errorf("evaluation: case metrics type %q, want manual", spec.Metrics.Type)
	}
	if hasO11YRequest(spec.Metrics) {
		return nil, fmt.Errorf("evaluation: manual case cannot contain O11Y request fields")
	}

	bundle := &CaseBundle{
		Version: CaseBundleVersionV1Alpha1,
		ID:      spec.ID,
		Source:  DataSourceManual,
		Input: CaseInput{
			DecisionAt: spec.DecisionAt,
			Component:  spec.Component,
			Current:    spec.Current,
			Metrics:    spec.Metrics.Series,
		},
		Baseline: spec.Baseline,
		Oracle:   spec.Oracle,
	}
	if err := validateCase(bundle); err != nil {
		return nil, err
	}
	return bundle, nil
}

// BuildO11YExportSpec combines one case's fixed request parameters with its
// versioned shared query profile.
func BuildO11YExportSpec(spec *CaseSpec, profile *MetricsProfile) (*O11YExportSpec, error) {
	if err := validateCaseSpecCommon(spec); err != nil {
		return nil, err
	}
	if err := validateO11YCaseRequest(spec); err != nil {
		return nil, err
	}
	if profile == nil {
		return nil, fmt.Errorf("evaluation: metrics profile is required")
	}
	if err := validateMetricsProfile(profile); err != nil {
		return nil, err
	}
	if profile.ID != spec.Metrics.Profile {
		return nil, fmt.Errorf("evaluation: metrics profile id %q, want %q", profile.ID, spec.Metrics.Profile)
	}
	if profile.Component != spec.Component {
		return nil, fmt.Errorf("evaluation: metrics profile component %q, want %q", profile.Component, spec.Component)
	}

	sources := make([]O11YSourceSpec, 0, len(profile.Sources))
	usedEndpoints := make(map[O11YMetricSource]struct{}, len(profile.Sources))
	for i := range profile.Sources {
		profileSource := &profile.Sources[i]
		baseURL, ok := spec.Metrics.Endpoints[profileSource.Type]
		if !ok || baseURL == "" {
			return nil, fmt.Errorf("evaluation: O11Y endpoint %q is required", profileSource.Type)
		}
		usedEndpoints[profileSource.Type] = struct{}{}

		queries := make([]O11YQuerySpec, len(profileSource.Queries))
		for j := range profileSource.Queries {
			queries[j] = profileSource.Queries[j]
			if profileSource.Queries[j].KeepLabels != nil {
				queries[j].KeepLabels = append([]string{}, profileSource.Queries[j].KeepLabels...)
			}
			queries[j].PromQL = strings.ReplaceAll(queries[j].PromQL, metricsProfileClusterIDPlaceholder, spec.Metrics.Target.ClusterID)
		}
		sources = append(sources, O11YSourceSpec{
			Type:    profileSource.Type,
			BaseURL: baseURL,
			Queries: queries,
		})
	}
	if len(usedEndpoints) != len(spec.Metrics.Endpoints) {
		return nil, fmt.Errorf("evaluation: O11Y endpoints must match metrics profile sources")
	}

	exportSpec := &O11YExportSpec{
		Version: O11YExportVersionV1Alpha1,
		Target:  *spec.Metrics.Target,
		Case: O11YCaseSpec{
			ID:         spec.ID,
			Component:  spec.Component,
			DecisionAt: spec.DecisionAt,
			Current:    spec.Current,
			Baseline:   spec.Baseline,
			Oracle:     spec.Oracle,
			Annotation: spec.Annotation,
		},
		Window:  *spec.Metrics.Window,
		Sources: sources,
	}
	if err := validateO11YExportSpec(exportSpec); err != nil {
		return nil, err
	}
	return exportSpec, nil
}

func validateCaseSpec(spec *CaseSpec) error {
	if err := validateCaseSpecCommon(spec); err != nil {
		return err
	}

	switch spec.Metrics.Type {
	case DataSourceManual:
		_, err := BuildManualCaseBundle(spec)
		return err
	case DataSourceO11Y:
		return validateO11YCaseRequest(spec)
	default:
		return fmt.Errorf("evaluation: case metrics type %q is unsupported", spec.Metrics.Type)
	}
}

func validateCaseSpecCommon(spec *CaseSpec) error {
	if spec == nil {
		return fmt.Errorf("evaluation: case spec is required")
	}
	if spec.Version != CaseBundleVersionV1Alpha1 {
		return fmt.Errorf("evaluation: case spec version %q is unsupported", spec.Version)
	}
	if spec.ID == "" {
		return fmt.Errorf("evaluation: case spec id is required")
	}
	if spec.Component == "" {
		return fmt.Errorf("evaluation: case spec component is required")
	}
	if spec.DecisionAt.IsZero() {
		return fmt.Errorf("evaluation: case spec decisionAt is required")
	}
	if err := validateCaseCapacity(&spec.Current, "case.current"); err != nil {
		return err
	}
	if err := validateCaseCapacity(&spec.Baseline, "case.baseline"); err != nil {
		return err
	}
	switch spec.Oracle.Class {
	case OracleClassUnnecessaryScaleOut, OracleClassUsefulScaleOut:
	default:
		return fmt.Errorf("evaluation: oracle class %q is unsupported", spec.Oracle.Class)
	}
	return validateCaseCapacity(&spec.Oracle.Expected, "case.oracle.expected")
}

func hasO11YRequest(metrics CaseMetrics) bool {
	return metrics.Profile != "" || metrics.Target != nil || metrics.Window != nil || len(metrics.Endpoints) > 0
}

func validateO11YCaseRequest(spec *CaseSpec) error {
	if spec.Metrics.Type != DataSourceO11Y {
		return fmt.Errorf("evaluation: case metrics type %q, want o11y", spec.Metrics.Type)
	}
	if len(spec.Metrics.Series) > 0 {
		return fmt.Errorf("evaluation: O11Y case cannot embed metric series")
	}
	if !isMetricsProfileReference(spec.Metrics.Profile) {
		return fmt.Errorf("evaluation: metrics profile reference %q is invalid", spec.Metrics.Profile)
	}
	if spec.Metrics.Target == nil || spec.Metrics.Window == nil {
		return fmt.Errorf("evaluation: O11Y case target and window are required")
	}
	if len(spec.Metrics.Endpoints) == 0 {
		return fmt.Errorf("evaluation: O11Y case endpoints are required")
	}
	return nil
}
