"""Simulation Plan Schema V2 (P3-M1).

Extends V1 with multi-fidelity strategy, search strategy,
calibration policy, and acceptance criteria per third-party review.

Backward compatible: V1 plans without these fields use defaults.
"""
from enum import Enum
from typing import Optional, List, Dict, Any
from pydantic import BaseModel, Field


# ============================================================
# Enums
# ============================================================

class StrategyMode(str, Enum):
    """Simulation strategy mode."""
    FAST_FEASIBLE = "fast_feasible"       # Default: constrained Bayesian / active learning
    PARETO_EXPLORATION = "pareto_exploration"  # Morris + LHS + Kriging + NSGA-II
    HIGH_FIDELITY_VALIDATION = "high_fidelity_validation"  # L2 + L3 verification
    ROBUSTNESS_CHECK = "robustness_check"  # Tolerance / disturbance analysis


class FidelityLevel(str, Enum):
    """Multi-fidelity levels L0-L4."""
    L0_ANALYTIC = "L0_analytic"                    # Analytic formulas + rule engine
    L1_MOTORCAD_EMAG = "L1_motorcad_emag"         # Motor-CAD fast EM model
    L2_MOTORCAD_LAB_THERM = "L2_motorcad_lab_therm"  # Motor-CAD Lab/Therm/Mech
    L3_MAXWELL_3D = "L3_maxwell_3d"               # Maxwell 3D / JMAG high-fidelity
    L4_ROBUSTNESS = "L4_robustness"                # Tolerance / prototype data


class SearchMethod(str, Enum):
    """Search/optimization method."""
    CONSTRAINED_BAYESIAN = "constrained_bayesian"  # Default: feasibility-first
    ACTIVE_LEARNING = "active_learning"             # Uncertainty-based sampling
    LHS_KRIGING_NSGA2 = "lhs_kriging_nsga2"        # Global Pareto exploration
    GRID_SCAN = "grid_scan"                          # Fixed full factorial
    TRUST_REGION = "trust_region"                    # Local trust region search


class ConfidenceGrade(str, Enum):
    """Result confidence grade A/B/C/D."""
    A = "A"  # Full multi-physics + high-fidelity + robustness, design freeze ready
    B = "B"  # Motor-CAD + at least one high-fidelity check, candidate ready
    C = "C"  # Motor-CAD only, internal discussion only, no external commitment
    D = "D"  # Analytic / surrogate only, reference only


class ConvergenceStatus(str, Enum):
    """Six types of convergence status (per review)."""
    # Solver convergence
    SOLVER_PASS = "SOLVER_PASS"
    SOLVER_FAIL = "SOLVER_FAIL"
    # Hard constraint convergence
    FEASIBLE = "FEASIBLE"
    INFEASIBLE = "INFEASIBLE"
    # Optimization convergence
    CONVERGED = "CONVERGED"
    STALLED = "STALLED"
    # Surrogate model trust
    MODEL_TRUSTED = "MODEL_TRUSTED"
    MODEL_UNCERTAIN = "MODEL_UNCERTAIN"
    # Cross-tool consistency
    HF_PASS = "HF_PASS"
    HF_FAIL = "HF_FAIL"
    # Robustness
    ROBUST = "ROBUST"
    FRAGILE = "FRAGILE"


# ============================================================
# Strategy Sub-models
# ============================================================

class FidelityStrategy(BaseModel):
    """Multi-fidelity execution strategy."""
    levels: List[FidelityLevel] = Field(
        default_factory=lambda: [
            FidelityLevel.L0_ANALYTIC,
            FidelityLevel.L1_MOTORCAD_EMAG,
        ],
        description="Enabled fidelity levels in execution order"
    )
    upgrade_rule: str = Field(
        default="top_candidates_only",
        description="Rule for upgrading to higher fidelity: top_candidates_only / threshold_based / all"
    )
    max_candidates_for_l3: int = Field(
        default=3, ge=1, le=10,
        description="Max candidates to send to L3 (Maxwell/JMAG)"
    )
    max_solver_cost_per_level: Optional[Dict[str, int]] = Field(
        default=None,
        description="Max solver calls per fidelity level, e.g. {'L1': 80, 'L2': 10}"
    )


class SearchStrategy(BaseModel):
    """Adaptive search strategy."""
    method: SearchMethod = Field(
        default=SearchMethod.CONSTRAINED_BAYESIAN,
        description="Search/optimization method"
    )
    initial_samples: int = Field(
        default=16, ge=4, le=100,
        description="Number of initial samples (LHS or from experience)"
    )
    batch_size: int = Field(
        default=4, ge=1, le=16,
        description="Number of points per adaptive batch"
    )
    max_solver_calls: int = Field(
        default=80, ge=10, le=500,
        description="Maximum total solver calls"
    )
    local_trust_region: bool = Field(
        default=True,
        description="Enable local trust region refinement after feasible region found"
    )
    trust_region_radius: Optional[float] = Field(
        default=None,
        description="Initial trust region radius as fraction of parameter range"
    )
    use_experience_seeds: bool = Field(
        default=True,
        description="Use similar experience cases as initial seeds"
    )


class CalibrationPolicy(BaseModel):
    """Cross-tool calibration policy (Motor-CAD vs Maxwell/JMAG)."""
    enabled: bool = Field(default=False, description="Enable cross-tool calibration")
    cross_tool_metrics: List[str] = Field(
        default_factory=lambda: ["torque_nm", "efficiency_pct", "axial_force_n"],
        description="Metrics to compare across tools"
    )
    tolerance: Dict[str, float] = Field(
        default_factory=lambda: {"torque_pct": 5.0, "efficiency_point": 1.0, "axial_force_pct": 10.0},
        description="Acceptable tolerance for cross-tool deviation"
    )
    correction_method: str = Field(
        default="additive",
        description="Correction method: additive / multiplicative / co-kriging"
    )
    feedback_to_surrogate: bool = Field(
        default=True,
        description="Feed calibration coefficients back to surrogate model and objective"
    )


class AcceptanceCriteria(BaseModel):
    """Acceptance criteria for convergence and validation."""
    hard_constraints: List[str] = Field(
        default_factory=list,
        description="Hard constraint expressions, e.g. ['torque_nm >= 10', 'temperature_c <= 120']"
    )
    soft_objectives: Optional[List[str]] = Field(
        default=None,
        description="Soft objective expressions for optimization"
    )
    cross_tool_tolerance: Optional[Dict[str, float]] = Field(
        default=None,
        description="Override cross-tool tolerance"
    )
    surrogate_max_uncertainty: float = Field(
        default=0.05, ge=0.01, le=0.5,
        description="Max surrogate model uncertainty for MODEL_TRUSTED status"
    )
    robustness_required: bool = Field(
        default=False,
        description="Require robustness check before final acceptance"
    )
    min_confidence_grade: ConfidenceGrade = Field(
        default=ConfidenceGrade.C,
        description="Minimum confidence grade for plan acceptance"
    )


class ParallelExecution(BaseModel):
    """Parallel execution configuration."""
    max_instances: int = Field(
        default=1, ge=1, le=8,
        description="Max parallel Motor-CAD instances"
    )
    model_copy_strategy: str = Field(
        default="per_instance",
        description="Model file copy strategy: per_instance / shared_readonly"
    )
    license_fail_policy: str = Field(
        default="queue_retry",
        description="Policy on license failure: queue_retry / fail_fast / reduce_instances"
    )


# ============================================================
# Schema V2 Main Model
# ============================================================

class SimulationPlanSchemaV2(BaseModel):
    """Extended simulation plan schema (V2) per third-party review.

    All new fields are optional for backward compatibility with V1 plans.
    """
    schema_version: str = Field(default="2.0", description="Schema version")
    strategy_mode: StrategyMode = Field(
        default=StrategyMode.FAST_FEASIBLE,
        description="Simulation strategy mode"
    )
    fidelity_strategy: Optional[FidelityStrategy] = Field(
        default=None,
        description="Multi-fidelity execution strategy"
    )
    search_strategy: Optional[SearchStrategy] = Field(
        default=None,
        description="Adaptive search strategy"
    )
    calibration_policy: Optional[CalibrationPolicy] = Field(
        default=None,
        description="Cross-tool calibration policy"
    )
    acceptance_criteria: Optional[AcceptanceCriteria] = Field(
        default=None,
        description="Acceptance criteria"
    )
    parallel_execution: Optional[ParallelExecution] = Field(
        default=None,
        description="Parallel execution configuration"
    )

    # V1 fields (preserved)
    model_path: Optional[str] = None
    topology: Optional[str] = None
    variables: List[Dict[str, Any]] = Field(default_factory=list)
    cases: List[Dict[str, Any]] = Field(default_factory=list)
    boundary_conditions: Optional[Dict[str, Any]] = None

    def get_effective_fidelity(self) -> FidelityStrategy:
        """Get fidelity strategy with defaults applied."""
        return self.fidelity_strategy or FidelityStrategy()

    def get_effective_search(self) -> SearchStrategy:
        """Get search strategy with defaults applied."""
        return self.search_strategy or SearchStrategy()

    def get_effective_acceptance(self) -> AcceptanceCriteria:
        """Get acceptance criteria with defaults applied."""
        return self.acceptance_criteria or AcceptanceCriteria()
