package test

import (
	"encoding/json"
	"fmt"
	"math"
	"os"
	"time"

	"github.com/yaoapp/yao/agent/context"
)

// Status represents the status of a test case execution
type Status string

const (
	// StatusPassed indicates the test passed
	StatusPassed Status = "passed"
	// StatusFailed indicates the test failed
	StatusFailed Status = "failed"
	// StatusSkipped indicates the test was skipped
	StatusSkipped Status = "skipped"
	// StatusError indicates a runtime error occurred
	StatusError Status = "error"
	// StatusTimeout indicates the test timed out
	StatusTimeout Status = "timeout"
)

// OutputFormat represents the output format for test reports
type OutputFormat string

const (
	// FormatJSON outputs JSON format (for CI integration)
	FormatJSON OutputFormat = "json"
	// FormatHTML outputs HTML format (for human review)
	FormatHTML OutputFormat = "html"
	// FormatMarkdown outputs Markdown format (for documentation)
	FormatMarkdown OutputFormat = "markdown"
)

// StabilityClass represents the stability classification of a test case
type StabilityClass string

const (
	// StabilityStable indicates 100% pass rate
	StabilityStable StabilityClass = "stable"
	// StabilityMostlyStable indicates 80-99% pass rate
	StabilityMostlyStable StabilityClass = "mostly_stable"
	// StabilityUnstable indicates 50-79% pass rate
	StabilityUnstable StabilityClass = "unstable"
	// StabilityHighlyUnstable indicates < 50% pass rate
	StabilityHighlyUnstable StabilityClass = "highly_unstable"
)

// InputMode represents the input mode for test cases
type InputMode string

const (
	// InputModeFile indicates input from a JSONL file
	InputModeFile InputMode = "file"
	// InputModeMessage indicates input from a direct message string
	InputModeMessage InputMode = "message"
	// InputModeScript indicates script test mode (testing agent handler scripts)
	InputModeScript InputMode = "script"
)

// Options represents the configuration options for running tests
type Options struct {
	// Input/Output
	// ===============================

	// Input is the input source: either a file path or a direct message
	Input string `json:"input"`

	// InputMode is the input mode (auto-detected from Input)
	InputMode InputMode `json:"input_mode"`

	// OutputFile is the path to write the test report
	// Format is determined by file extension (.json, .html, .md)
	OutputFile string `json:"output_file"`

	// Agent Selection
	// ===============================

	// AgentID is the explicit agent ID to test (optional)
	// If not set, agent is resolved from InputFile path
	AgentID string `json:"agent_id,omitempty"`

	// Connector overrides the agent's default connector (optional)
	Connector string `json:"connector,omitempty"`

	// Test Environment
	// ===============================

	// UserID is the test user ID (-u flag)
	UserID string `json:"user_id,omitempty"`

	// TeamID is the test team ID (-t flag)
	TeamID string `json:"team_id,omitempty"`

	// Locale is the locale for the test context (default: "en-us")
	Locale string `json:"locale,omitempty"`

	// ContextFile is the path to a JSON file containing custom context data (-ctx flag)
	// This allows full customization of authorized info, metadata, etc.
	ContextFile string `json:"context_file,omitempty"`

	// ContextData is the parsed context data from ContextFile
	// This is populated internally after loading the file
	ContextData *ContextConfig `json:"-"`

	// Execution
	// ===============================

	// Timeout is the default timeout for each test case
	// Can be overridden per test case
	Timeout time.Duration `json:"timeout,omitempty"`

	// Parallel is the number of tests to run in parallel
	// Default is 1 (sequential execution)
	Parallel int `json:"parallel,omitempty"`

	// Runs is the number of times to run each test case
	// Default is 1. When > 1, stability metrics are collected
	Runs int `json:"runs,omitempty"`

	// Reporting
	// ===============================

	// ReporterID is the reporter agent ID for custom report generation
	// If not set, default JSONL format is used
	ReporterID string `json:"reporter_id,omitempty"`

	// Behavior
	// ===============================

	// Verbose enables verbose output during test execution
	Verbose bool `json:"verbose,omitempty"`

	// FailFast stops execution on first failure
	FailFast bool `json:"fail_fast,omitempty"`

	// Run is a regex pattern to filter which tests to run (similar to go test -run)
	// Only tests matching the pattern will be executed
	// Example: "TestSystem" matches TestSystemReady, TestSystemError, etc.
	Run string `json:"run,omitempty"`

	// BeforeAll is the global before script (e.g., "scripts:tests.env.BeforeAll")
	// Called once before all test cases
	BeforeAll string `json:"before_all,omitempty"`

	// AfterAll is the global after script (e.g., "scripts:tests.env.AfterAll")
	// Called once after all test cases
	AfterAll string `json:"after_all,omitempty"`

	// DryRun generates test cases without running them
	// Useful for previewing agent-generated test cases
	DryRun bool `json:"dry_run,omitempty"`

	// Simulator is the default simulator agent ID for dynamic mode
	// Can be overridden per test case in JSONL
	Simulator string `json:"simulator,omitempty"`

	// JSONOutput outputs results in JSON format for AI/script consumption
	JSONOutput bool `json:"json_output,omitempty"`
}

// ContextConfig represents custom context configuration from JSON file
// This allows full customization of the test context including authorized info
type ContextConfig struct {
	// ChatID is the chat session identifier
	// Used to maintain session state across turns in dynamic tests
	ChatID string `json:"chat_id,omitempty"`

	// Authorized contains custom authorization data
	Authorized *AuthorizedConfig `json:"authorized,omitempty"`

	// Metadata contains custom metadata to pass to the context
	Metadata map[string]interface{} `json:"metadata,omitempty"`

	// Client contains custom client information
	Client *ClientConfig `json:"client,omitempty"`

	// Locale overrides the locale setting
	Locale string `json:"locale,omitempty"`

	// Referer overrides the referer setting
	Referer string `json:"referer,omitempty"`
}

// AuthorizedConfig represents custom authorization configuration
// Matches the structure of types.AuthorizedInfo from openapi/oauth/types
type AuthorizedConfig struct {
	// Sub is the subject identifier (JWT sub claim)
	Sub string `json:"sub,omitempty"`

	// ClientID is the OAuth client ID
	ClientID string `json:"client_id,omitempty"`

	// Scope is the access scope
	Scope string `json:"scope,omitempty"`

	// SessionID is the session identifier
	SessionID string `json:"session_id,omitempty"`

	// UserID is the user identifier
	UserID string `json:"user_id,omitempty"`

	// TeamID is the team identifier
	TeamID string `json:"team_id,omitempty"`

	// TenantID is the tenant identifier
	TenantID string `json:"tenant_id,omitempty"`

	// RememberMe is the remember me flag
	RememberMe bool `json:"remember_me,omitempty"`

	// Constraints contains data access constraints (set by ACL enforcement)
	Constraints *DataConstraintsConfig `json:"constraints,omitempty"`
}

// DataConstraintsConfig represents data access constraints
// Matches the structure of types.DataConstraints from openapi/oauth/types
type DataConstraintsConfig struct {
	// OwnerOnly - only access owner's data
	OwnerOnly bool `json:"owner_only,omitempty"`

	// CreatorOnly - only access creator's data
	CreatorOnly bool `json:"creator_only,omitempty"`

	// EditorOnly - only access editor's data
	EditorOnly bool `json:"editor_only,omitempty"`

	// TeamOnly - only access team's data (filter by team_id)
	TeamOnly bool `json:"team_only,omitempty"`

	// Extra contains user-defined constraints (department, region, etc.)
	Extra map[string]interface{} `json:"extra,omitempty"`
}

// ClientConfig represents custom client configuration
type ClientConfig struct {
	// Type is the client type (e.g., "web", "mobile", "test")
	Type string `json:"type,omitempty"`

	// UserAgent is the client user agent string
	UserAgent string `json:"user_agent,omitempty"`

	// IP is the client IP address
	IP string `json:"ip,omitempty"`
}

// Environment configures the test execution context
type Environment struct {
	// UserID is the user ID for authorized info (-u flag)
	UserID string `json:"user_id"`

	// TeamID is the team ID for authorized info (-t flag)
	TeamID string `json:"team_id"`

	// Locale is the locale (default: "en-us")
	Locale string `json:"locale"`

	// ClientType is the client type (default: "test")
	ClientType string `json:"client_type"`

	// ClientIP is the client IP (default: "127.0.0.1")
	ClientIP string `json:"client_ip"`

	// Referer is the request referer (default: "test")
	Referer string `json:"referer"`

	// Accept is the accept format (default: "standard")
	Accept string `json:"accept"`

	// ContextConfig contains custom context configuration (from -ctx flag)
	ContextConfig *ContextConfig `json:"-"`
}

// NewEnvironment creates a new test environment with defaults
func NewEnvironment(userID, teamID string) *Environment {
	env := &Environment{
		UserID:     userID,
		TeamID:     teamID,
		Locale:     "en-us",
		ClientType: "test",
		ClientIP:   "127.0.0.1",
		Referer:    "test",
		Accept:     "standard",
	}

	// Apply defaults if not set
	if env.UserID == "" {
		env.UserID = "test-user"
	}
	if env.TeamID == "" {
		env.TeamID = "test-team"
	}

	return env
}

// NewEnvironmentWithContext creates a new test environment with custom context config
func NewEnvironmentWithContext(userID, teamID string, ctxConfig *ContextConfig) *Environment {
	env := NewEnvironment(userID, teamID)

	if ctxConfig == nil {
		return env
	}

	env.ContextConfig = ctxConfig

	// Override with context config values
	if ctxConfig.Locale != "" {
		env.Locale = ctxConfig.Locale
	}
	if ctxConfig.Referer != "" {
		env.Referer = ctxConfig.Referer
	}
	if ctxConfig.Client != nil {
		if ctxConfig.Client.Type != "" {
			env.ClientType = ctxConfig.Client.Type
		}
		if ctxConfig.Client.IP != "" {
			env.ClientIP = ctxConfig.Client.IP
		}
	}
	if ctxConfig.Authorized != nil {
		if ctxConfig.Authorized.UserID != "" {
			env.UserID = ctxConfig.Authorized.UserID
		}
		// TeamID takes precedence over TenantID for team override
		if ctxConfig.Authorized.TeamID != "" {
			env.TeamID = ctxConfig.Authorized.TeamID
		} else if ctxConfig.Authorized.TenantID != "" {
			env.TeamID = ctxConfig.Authorized.TenantID
		}
	}

	return env
}

// LoadContextConfig loads context configuration from a JSON file
func LoadContextConfig(filePath string) (*ContextConfig, error) {
	resolvedPath := ResolvePathWithYaoRoot(filePath)
	data, err := os.ReadFile(resolvedPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read context file: %w", err)
	}

	var config ContextConfig
	if err := json.Unmarshal(data, &config); err != nil {
		return nil, fmt.Errorf("failed to parse context file: %w", err)
	}

	return &config, nil
}

// Case represents a single test case loaded from JSONL
type Case struct {
	// ID is the unique identifier for this test case (e.g., "T001")
	ID string `json:"id"`

	// Input is the test input, can be:
	// - string: simple text input
	// - map (Message): single message with role and content
	// - []map ([]Message): conversation history
	Input interface{} `json:"input"`

	// Expected is the expected output for validation (optional)
	// If set, the actual output will be compared against this
	Expected interface{} `json:"expected,omitempty"`

	// Assert defines custom assertion rules (optional)
	// If set, these rules will be used instead of simple expected comparison
	// Can be a single assertion or an array of assertions
	Assert interface{} `json:"assert,omitempty"`

	// Environment (per-test case, can be overridden by command line flags)
	// ===============================

	// UserID is the user ID for this test case (overridden by -u flag)
	UserID string `json:"user,omitempty"`

	// TeamID is the team ID for this test case (overridden by -t flag)
	TeamID string `json:"team,omitempty"`

	// Metadata contains additional metadata for the test case
	// This is passed to ctx.Metadata and can be used by Create Hook
	Metadata map[string]interface{} `json:"metadata,omitempty"`

	// Options contains context options for this test case
	// Supports: connector, skip (history, trace, output, keyword, search), mode
	Options *CaseOptions `json:"options,omitempty"`

	// Skip indicates whether to skip this test case
	Skip bool `json:"skip,omitempty"`

	// Timeout overrides the default timeout for this test case
	// Format: "30s", "1m", "2m30s"
	Timeout string `json:"timeout,omitempty"`

	// Before script function (e.g., "scripts:tests.env.Before")
	// Called before the test case runs, returns data passed to After
	Before string `json:"before,omitempty"`

	// After script function (e.g., "scripts:tests.env.After")
	// Called after the test case completes (pass or fail)
	After string `json:"after,omitempty"`

	// Dynamic Mode Fields
	// ===============================

	// Simulator configures the user simulator for dynamic testing
	// When set, the test runs in dynamic mode with multi-turn conversation
	Simulator *Simulator `json:"simulator,omitempty"`

	// Checkpoints define validation points for dynamic testing
	// Each checkpoint is checked after every agent response
	Checkpoints []*Checkpoint `json:"checkpoints,omitempty"`

	// MaxTurns is the maximum number of conversation turns (default: 20)
	MaxTurns int `json:"max_turns,omitempty"`
}

// Simulator configures the user simulator for dynamic testing
type Simulator struct {
	// Use is the simulator agent ID (no prefix needed)
	Use string `json:"use"`

	// Options for the simulator agent
	Options *SimulatorOptions `json:"options,omitempty"`
}

// SimulatorOptions configures simulator behavior
type SimulatorOptions struct {
	// Metadata passed to the simulator agent
	// Common fields: persona, goal, style
	Metadata map[string]interface{} `json:"metadata,omitempty"`

	// Connector overrides the simulator's default connector
	Connector string `json:"connector,omitempty"`
}

// Checkpoint defines a validation point in dynamic testing
type Checkpoint struct {
	// ID is the unique identifier for this checkpoint
	ID string `json:"id"`

	// Description is a human-readable description
	Description string `json:"description,omitempty"`

	// Assert defines the assertion to validate
	// Same format as Case.Assert
	Assert interface{} `json:"assert"`

	// After specifies checkpoint IDs that must be reached before this one
	// Used to enforce ordering (e.g., "ask_type" must come before "confirm")
	After []string `json:"after,omitempty"`

	// Required indicates if this checkpoint must be reached (default: true)
	// Optional checkpoints don't cause test failure if not reached
	Required *bool `json:"required,omitempty"`
}

// CaseOptions represents per-test-case context options
// Maps to context.Options fields
type CaseOptions struct {
	// Connector overrides the agent's default connector
	Connector string `json:"connector,omitempty"`

	// Skip configuration
	Skip *CaseSkipOptions `json:"skip,omitempty"`

	// DisableGlobalPrompts temporarily disables global prompts for this request
	DisableGlobalPrompts bool `json:"disable_global_prompts,omitempty"`

	// Search mode, default is true (use pointer to distinguish unset from false)
	Search *bool `json:"search,omitempty"`

	// Mode is the agent mode (default: "chat")
	Mode string `json:"mode,omitempty"`

	// Metadata for passing custom data to hooks (e.g., scenario selection)
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

// CaseSkipOptions represents skip configuration for a test case
// Maps to context.Skip fields
type CaseSkipOptions struct {
	History bool `json:"history,omitempty"` // Skip history loading
	Trace   bool `json:"trace,omitempty"`   // Skip trace logging
	Output  bool `json:"output,omitempty"`  // Skip output to client
	Keyword bool `json:"keyword,omitempty"` // Skip keyword extraction
	Search  bool `json:"search,omitempty"`  // Skip auto search
}

// Assertion represents a single assertion rule
type Assertion struct {
	// Type is the assertion type:
	// - "equals": exact match (default if expected is set)
	// - "contains": output contains the expected string/value
	// - "not_contains": output does not contain the string/value
	// - "json_path": extract value using JSON path and compare
	// - "regex": match output against regex pattern
	// - "script": run a custom assertion script
	// - "type": check output type (string, object, array, number, boolean)
	// - "schema": validate against JSON schema
	// - "agent": use an agent to validate the response
	Type string `json:"type"`

	// Value is the expected value or pattern (depends on type)
	Value interface{} `json:"value,omitempty"`

	// Path is the JSON path for json_path assertions (e.g., "$.need_search")
	Path string `json:"path,omitempty"`

	// Script is the assertion script name for script assertions
	// The script receives (output, input, expected) and returns {pass: bool, message: string}
	Script string `json:"script,omitempty"`

	// Use specifies the agent/script for validation
	// For agent assertions: "agents:tests.validator-agent" (with prefix)
	// For script assertions: "scripts:tests.validate" (with prefix)
	Use string `json:"use,omitempty"`

	// Options for agent-driven assertions (aligned with context.Options)
	Options *AssertionOptions `json:"options,omitempty"`

	// Message is a custom failure message
	Message string `json:"message,omitempty"`

	// Negate inverts the assertion result
	Negate bool `json:"negate,omitempty"`
}

// AssertionOptions for agent-driven assertions
type AssertionOptions struct {
	// Connector overrides the agent's default connector
	Connector string `json:"connector,omitempty"`

	// Metadata contains custom data passed to the validator agent
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

// AssertionResult represents the result of an assertion
type AssertionResult struct {
	// Passed indicates whether the assertion passed
	Passed bool `json:"passed"`

	// Message describes the assertion result
	Message string `json:"message,omitempty"`

	// Assertion is the original assertion that was evaluated
	Assertion *Assertion `json:"assertion,omitempty"`

	// Actual is the actual value that was compared
	Actual interface{} `json:"actual,omitempty"`

	// Expected is the expected value
	Expected interface{} `json:"expected,omitempty"`
}

// GetEnvironment returns the effective test environment for this test case
// Priority: command line flags > context config > test case fields > defaults
func (tc *Case) GetEnvironment(opts *Options) *Environment {
	// Start with context config if available, otherwise use defaults
	var env *Environment
	if opts != nil && opts.ContextData != nil {
		env = NewEnvironmentWithContext("", "", opts.ContextData)
	} else {
		env = NewEnvironment("", "")
	}

	// Apply test case specific values
	if tc.UserID != "" {
		env.UserID = tc.UserID
	}
	if tc.TeamID != "" {
		env.TeamID = tc.TeamID
	}

	// Apply command line overrides (highest priority)
	if opts != nil {
		if opts.UserID != "" {
			env.UserID = opts.UserID
		}
		if opts.TeamID != "" {
			env.TeamID = opts.TeamID
		}
		if opts.Locale != "" {
			env.Locale = opts.Locale
		}
	}

	return env
}

// GetMessages converts the Input to a slice of context.Message
// This handles all input formats: string, Message, []Message
func (tc *Case) GetMessages() ([]context.Message, error) {
	return ParseInput(tc.Input)
}

// GetMessagesWithOptions converts the Input to a slice of context.Message with options
// This handles all input formats: string, Message, []Message
// It also processes file:// references in content parts
func (tc *Case) GetMessagesWithOptions(opts *InputOptions) ([]context.Message, error) {
	return ParseInputWithOptions(tc.Input, opts)
}

// GetTimeout returns the timeout duration for this test case
// Returns the override timeout if set, otherwise returns the default
func (tc *Case) GetTimeout(defaultTimeout time.Duration) time.Duration {
	if tc.Timeout == "" {
		return defaultTimeout
	}
	d, err := time.ParseDuration(tc.Timeout)
	if err != nil {
		return defaultTimeout
	}
	return d
}

// Result represents the result of running a single test case
type Result struct {
	// ID is the test case identifier
	ID string `json:"id"`

	// Status is the test execution status
	Status Status `json:"status"`

	// Input is the original test input (for reference in reports)
	Input interface{} `json:"input"`

	// Output is the actual output from the agent
	Output interface{} `json:"output,omitempty"`

	// Expected is the expected output (if specified in test case)
	Expected interface{} `json:"expected,omitempty"`

	// DurationMs is the execution duration in milliseconds
	DurationMs int64 `json:"duration_ms"`

	// Error contains the error message if status is failed/error/timeout
	Error string `json:"error,omitempty"`

	// Trace contains full execution details for AI-driven failure diagnosis.
	// Includes LLM completion info, all tool/MCP calls with arguments and results, and Next hook data.
	Trace *Trace `json:"trace,omitempty"`

	// Options contains the context options used for this test case
	Options *CaseOptions `json:"options,omitempty"`

	// Metadata contains additional result metadata
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

// Trace contains full execution details for AI-driven failure diagnosis.
// It captures the complete chain: LLM completion -> tool/MCP calls -> Next hook,
// enabling AI to understand exactly what happened during test execution.
type Trace struct {
	Completion *CompletionTrace `json:"completion,omitempty"`
	ToolCalls  []TraceToolCall  `json:"tool_calls,omitempty"`
	Next       interface{}      `json:"next,omitempty"`
}

// CompletionTrace captures LLM completion details.
type CompletionTrace struct {
	Model            string      `json:"model,omitempty"`
	Role             string      `json:"role,omitempty"`
	Content          interface{} `json:"content,omitempty"`
	ReasoningContent string      `json:"reasoning_content,omitempty"`
	Refusal          string      `json:"refusal,omitempty"`
}

// TraceToolCall contains the complete lifecycle of a single tool/MCP call:
// request (ID, arguments) matched with response (server, result, error).
// MCP tool calls may execute in parallel; the ID field links request to response.
type TraceToolCall struct {
	ID        string      `json:"id,omitempty"`
	Server    string      `json:"server,omitempty"`
	Tool      string      `json:"tool"`
	Arguments interface{} `json:"arguments,omitempty"`
	Result    interface{} `json:"result,omitempty"`
	Error     string      `json:"error,omitempty"`
}

// RunDetail represents the result of a single run in stability testing
type RunDetail struct {
	// Run is the run number (1-based)
	Run int `json:"run"`

	// Status is the execution status for this run
	Status Status `json:"status"`

	// DurationMs is the execution duration in milliseconds
	DurationMs int64 `json:"duration_ms"`

	// Output is the output from this run
	Output interface{} `json:"output,omitempty"`

	// Error contains the error message if this run failed
	Error string `json:"error,omitempty"`
}

// StabilityResult represents the stability analysis result for a test case
type StabilityResult struct {
	// ID is the test case identifier
	ID string `json:"id"`

	// Input is the original test input
	Input interface{} `json:"input"`

	// Expected is the expected output (if specified)
	Expected interface{} `json:"expected,omitempty"`

	// Runs is the total number of runs
	Runs int `json:"runs"`

	// Passed is the number of runs that passed
	Passed int `json:"passed"`

	// Failed is the number of runs that failed
	Failed int `json:"failed"`

	// PassRate is the pass rate percentage (0-100)
	PassRate float64 `json:"pass_rate"`

	// Consistency is a measure of output consistency (0-1)
	// 1.0 means all outputs are identical, lower values indicate variation
	Consistency float64 `json:"consistency"`

	// Stable indicates whether the test is considered stable
	Stable bool `json:"stable"`

	// StabilityClass is the stability classification
	StabilityClass StabilityClass `json:"stability_class"`

	// Timing statistics
	AvgDurationMs  float64 `json:"avg_duration_ms"`
	MinDurationMs  int64   `json:"min_duration_ms"`
	MaxDurationMs  int64   `json:"max_duration_ms"`
	StdDeviationMs float64 `json:"std_deviation_ms"`

	// RunDetails contains details for each run
	RunDetails []*RunDetail `json:"run_details"`
}

// CalculateStability calculates stability metrics from run details
func (sr *StabilityResult) CalculateStability() {
	if len(sr.RunDetails) == 0 {
		return
	}

	sr.Runs = len(sr.RunDetails)
	sr.Passed = 0
	sr.Failed = 0

	var totalDuration int64
	sr.MinDurationMs = math.MaxInt64
	sr.MaxDurationMs = 0

	for _, rd := range sr.RunDetails {
		if rd.Status == StatusPassed {
			sr.Passed++
		} else {
			sr.Failed++
		}

		totalDuration += rd.DurationMs
		if rd.DurationMs < sr.MinDurationMs {
			sr.MinDurationMs = rd.DurationMs
		}
		if rd.DurationMs > sr.MaxDurationMs {
			sr.MaxDurationMs = rd.DurationMs
		}
	}

	// Calculate pass rate
	sr.PassRate = float64(sr.Passed) / float64(sr.Runs) * 100

	// Calculate average duration
	sr.AvgDurationMs = float64(totalDuration) / float64(sr.Runs)

	// Calculate standard deviation
	var sumSquares float64
	for _, rd := range sr.RunDetails {
		diff := float64(rd.DurationMs) - sr.AvgDurationMs
		sumSquares += diff * diff
	}
	sr.StdDeviationMs = math.Sqrt(sumSquares / float64(sr.Runs))

	// Determine stability classification
	sr.StabilityClass = ClassifyStability(sr.PassRate)
	sr.Stable = sr.PassRate == 100

	// Calculate consistency (simplified: based on pass rate)
	sr.Consistency = sr.PassRate / 100
}

// ClassifyStability returns the stability classification based on pass rate
func ClassifyStability(passRate float64) StabilityClass {
	switch {
	case passRate == 100:
		return StabilityStable
	case passRate >= 80:
		return StabilityMostlyStable
	case passRate >= 50:
		return StabilityUnstable
	default:
		return StabilityHighlyUnstable
	}
}

// Summary contains aggregated statistics for the test run
type Summary struct {
	// Total number of test cases
	Total int `json:"total"`

	// Passed number of test cases that passed
	Passed int `json:"passed"`

	// Failed number of test cases that failed
	Failed int `json:"failed"`

	// Skipped number of test cases that were skipped
	Skipped int `json:"skipped"`

	// Errors number of test cases with runtime errors
	Errors int `json:"errors"`

	// Timeouts number of test cases that timed out
	Timeouts int `json:"timeouts"`

	// DurationMs is the total execution duration in milliseconds
	DurationMs int64 `json:"duration_ms"`

	// AgentID is the ID of the agent being tested
	AgentID string `json:"agent_id"`

	// AgentPath is the file path of the agent (for path-based resolution)
	AgentPath string `json:"agent_path,omitempty"`

	// Connector is the connector used for the test
	Connector string `json:"connector"`

	// Stability metrics (when Runs > 1)
	// ===============================

	// RunsPerCase is the number of runs per test case
	RunsPerCase int `json:"runs_per_case,omitempty"`

	// TotalRuns is the total number of runs (Total * RunsPerCase)
	TotalRuns int `json:"total_runs,omitempty"`

	// OverallPassRate is the overall pass rate percentage
	OverallPassRate float64 `json:"overall_pass_rate,omitempty"`

	// StableCases is the number of cases with 100% pass rate
	StableCases int `json:"stable_cases,omitempty"`

	// UnstableCases is the number of cases with < 100% pass rate
	UnstableCases int `json:"unstable_cases,omitempty"`
}

// Report represents the complete test report
type Report struct {
	// Summary contains aggregated statistics
	Summary *Summary `json:"summary"`

	// Environment contains the test environment configuration
	Environment *Environment `json:"environment,omitempty"`

	// Results contains individual test results (for single run)
	Results []*Result `json:"results,omitempty"`

	// StabilityResults contains stability analysis results (for multiple runs)
	StabilityResults []*StabilityResult `json:"stability_results,omitempty"`

	// Metadata contains additional report metadata
	Metadata *ReportMetadata `json:"metadata"`
}

// ReportMetadata contains metadata about the test report
type ReportMetadata struct {
	// StartedAt is when the test run started
	StartedAt time.Time `json:"started_at"`

	// CompletedAt is when the test run completed
	CompletedAt time.Time `json:"completed_at"`

	// Version is the Yao version
	Version string `json:"version"`

	// InputFile is the path to the input file
	InputFile string `json:"input_file"`

	// OutputFile is the path to the output file
	OutputFile string `json:"output_file"`

	// Options contains the test options used
	Options *Options `json:"options,omitempty"`
}

// HasFailures returns true if there are any failed, error, or timeout tests
func (r *Report) HasFailures() bool {
	return r.Summary.Failed > 0 || r.Summary.Errors > 0 || r.Summary.Timeouts > 0
}

// PassRate returns the pass rate as a percentage (0-100)
func (r *Report) PassRate() float64 {
	if r.Summary.Total == 0 {
		return 0
	}
	return float64(r.Summary.Passed) / float64(r.Summary.Total) * 100
}

// IsStabilityTest returns true if this is a stability test (multiple runs)
func (r *Report) IsStabilityTest() bool {
	return r.Summary.RunsPerCase > 1
}

// AgentInfo contains information about the agent being tested
type AgentInfo struct {
	// ID is the agent identifier
	ID string `json:"id"`

	// Name is the human-readable name
	Name string `json:"name"`

	// Description is the agent description
	Description string `json:"description,omitempty"`

	// Path is the file system path to the agent
	Path string `json:"path"`

	// Connector is the default connector
	Connector string `json:"connector"`

	// Type is the agent type (e.g., "worker", "assistant")
	Type string `json:"type,omitempty"`
}

// ReporterInput is the input passed to a custom reporter agent
type ReporterInput struct {
	// Report is the test report to format
	Report *Report `json:"report"`

	// Format is the desired output format
	Format string `json:"format"`

	// Options contains additional formatting options
	Options *ReporterOptions `json:"options,omitempty"`
}

// ReporterOptions contains options for custom reporter agents
type ReporterOptions struct {
	// Verbose includes detailed output in the report
	Verbose bool `json:"verbose,omitempty"`

	// IncludeOutputs includes full outputs in the report
	IncludeOutputs bool `json:"include_outputs,omitempty"`

	// IncludeInputs includes full inputs in the report
	IncludeInputs bool `json:"include_inputs,omitempty"`

	// MaxOutputLength limits the output length in the report
	MaxOutputLength int `json:"max_output_length,omitempty"`

	// Theme is the report theme (for HTML reports)
	Theme string `json:"theme,omitempty"`

	// Title is the report title
	Title string `json:"title,omitempty"`
}
