"""Tests for EngineeringValidator and related classes."""

import pytest
from pathlib import Path

from optimization_engine.interview.engineering_validator import (
    MaterialsDatabase,
    AntiPatternDetector,
    EngineeringValidator,
    ValidationResult,
    AntiPattern,
    Material,
)
from optimization_engine.interview.interview_state import InterviewState


class TestMaterial:
    """Tests for Material dataclass."""

    def test_properties(self):
        """Test material property accessors."""
        mat = Material(
            id="test",
            names=["test material"],
            category="test",
            properties={
                "density_kg_m3": 2700,
                "yield_stress_mpa": 276,
                "ultimate_stress_mpa": 310,
                "elastic_modulus_gpa": 69,
            }
        )

        assert mat.density == 2700
        assert mat.yield_stress == 276
        assert mat.ultimate_stress == 310
        assert mat.elastic_modulus == 69

    def test_get_safe_stress(self):
        """Test getting safe stress with safety factor."""
        mat = Material(
            id="test",
            names=["test"],
            category="test",
            properties={"yield_stress_mpa": 300},
            recommended_safety_factors={"static": 1.5, "fatigue": 3.0}
        )

        safe = mat.get_safe_stress("static")
        assert safe == 200.0  # 300 / 1.5

        safe_fatigue = mat.get_safe_stress("fatigue")
        assert safe_fatigue == 100.0  # 300 / 3.0


class TestMaterialsDatabase:
    """Tests for MaterialsDatabase."""

    def test_load_materials(self):
        """Test that materials are loaded from JSON."""
        db = MaterialsDatabase()
        assert len(db.materials) > 0
        # Check for al_6061_t6 (the actual ID in the database)
        assert "al_6061_t6" in db.materials

    def test_get_material_exact(self):
        """Test exact material lookup."""
        db = MaterialsDatabase()
        mat = db.get_material("al_6061_t6")
        assert mat is not None
        assert mat.id == "al_6061_t6"
        assert mat.yield_stress is not None

    def test_get_material_by_name(self):
        """Test material lookup by name."""
        db = MaterialsDatabase()

        # Test lookup by one of the indexed names
        mat = db.get_material("aluminum 6061-t6")
        assert mat is not None
        assert "6061" in mat.id.lower() or "al" in mat.id.lower()

    def test_get_material_fuzzy(self):
        """Test fuzzy material matching."""
        db = MaterialsDatabase()

        # Test various ways users might refer to aluminum
        mat = db.get_material("6061-t6")
        assert mat is not None

    def test_get_material_not_found(self):
        """Test material not found returns None."""
        db = MaterialsDatabase()
        mat = db.get_material("unobtanium")
        assert mat is None

    def test_get_yield_stress(self):
        """Test getting yield stress for material."""
        db = MaterialsDatabase()
        yield_stress = db.get_yield_stress("al_6061_t6")
        assert yield_stress is not None
        assert yield_stress > 200  # Al 6061-T6 is ~276 MPa

    def test_validate_stress_limit_valid(self):
        """Test stress validation - valid case."""
        db = MaterialsDatabase()

        # Below yield - should pass
        result = db.validate_stress_limit("al_6061_t6", 200)
        assert result.valid

    def test_validate_stress_limit_over_yield(self):
        """Test stress validation - over yield."""
        db = MaterialsDatabase()

        # Above yield - should have warning
        result = db.validate_stress_limit("al_6061_t6", 300)
        # It's valid=True but with warning severity
        assert result.severity in ["warning", "error"]

    def test_list_materials(self):
        """Test listing all materials."""
        db = MaterialsDatabase()
        materials = db.list_materials()
        assert len(materials) >= 10  # We should have at least 10 materials
        # Returns Material objects, not strings
        assert all(isinstance(m, Material) for m in materials)
        assert any("aluminum" in m.id.lower() or "al" in m.id.lower() for m in materials)

    def test_list_materials_by_category(self):
        """Test filtering materials by category."""
        db = MaterialsDatabase()
        steel_materials = db.list_materials(category="steel")
        assert len(steel_materials) > 0
        assert all(m.category == "steel" for m in steel_materials)


class TestAntiPatternDetector:
    """Tests for AntiPatternDetector."""

    def test_load_patterns(self):
        """Test pattern loading from JSON."""
        detector = AntiPatternDetector()
        assert len(detector.patterns) > 0

    def test_check_all_mass_no_constraint(self):
        """Test detection of mass minimization without constraints."""
        detector = AntiPatternDetector()
        state = InterviewState()

        # Set up mass minimization without constraints
        state.answers["objectives"] = [{"goal": "minimize_mass"}]
        state.answers["constraints"] = []

        patterns = detector.check_all(state, {})
        pattern_ids = [p.id for p in patterns]
        assert "mass_no_constraint" in pattern_ids

    def test_check_all_no_pattern_when_constraint_present(self):
        """Test no pattern when constraints are properly set."""
        detector = AntiPatternDetector()
        state = InterviewState()

        # Set up mass minimization WITH constraints
        state.answers["objectives"] = [{"goal": "minimize_mass"}]
        state.answers["constraints"] = [{"type": "stress", "threshold": 200}]

        patterns = detector.check_all(state, {})
        pattern_ids = [p.id for p in patterns]
        assert "mass_no_constraint" not in pattern_ids

    def test_check_all_bounds_too_wide(self):
        """Test detection of overly wide bounds."""
        detector = AntiPatternDetector()
        state = InterviewState()

        # Set up design variables with very wide bounds
        state.answers["design_variables"] = [
            {"name": "thickness", "min": 0.1, "max": 100}  # 1000x range
        ]

        patterns = detector.check_all(state, {})
        # Detector runs without error - pattern detection depends on implementation
        assert isinstance(patterns, list)

    def test_check_all_too_many_objectives(self):
        """Test detection of too many objectives."""
        detector = AntiPatternDetector()
        state = InterviewState()

        # Set up 4 objectives (above recommended 3)
        state.answers["objectives"] = [
            {"goal": "minimize_mass"},
            {"goal": "minimize_stress"},
            {"goal": "maximize_frequency"},
            {"goal": "minimize_displacement"}
        ]

        patterns = detector.check_all(state, {})
        pattern_ids = [p.id for p in patterns]
        assert "too_many_objectives" in pattern_ids

    def test_pattern_has_severity(self):
        """Test that patterns have correct severity."""
        detector = AntiPatternDetector()
        state = InterviewState()

        state.answers["objectives"] = [{"goal": "minimize_mass"}]
        state.answers["constraints"] = []

        patterns = detector.check_all(state, {})
        mass_pattern = next((p for p in patterns if p.id == "mass_no_constraint"), None)

        assert mass_pattern is not None
        assert mass_pattern.severity in ["error", "warning"]

    def test_pattern_has_fix_suggestion(self):
        """Test that patterns have fix suggestions."""
        detector = AntiPatternDetector()
        state = InterviewState()

        state.answers["objectives"] = [{"goal": "minimize_mass"}]
        state.answers["constraints"] = []

        patterns = detector.check_all(state, {})
        mass_pattern = next((p for p in patterns if p.id == "mass_no_constraint"), None)

        assert mass_pattern is not None
        assert mass_pattern.fix_suggestion is not None
        assert len(mass_pattern.fix_suggestion) > 0


class TestEngineeringValidator:
    """Tests for EngineeringValidator."""

    def test_validate_constraint_stress(self):
        """Test stress constraint validation."""
        validator = EngineeringValidator()

        # Valid stress constraint
        result = validator.validate_constraint(
            constraint_type="stress",
            value=200,
            material="al_6061_t6"
        )
        assert result.valid

    def test_validate_constraint_displacement(self):
        """Test displacement constraint validation."""
        validator = EngineeringValidator()

        # Reasonable displacement
        result = validator.validate_constraint(
            constraint_type="displacement",
            value=0.5
        )
        assert result.valid

    def test_validate_constraint_frequency(self):
        """Test frequency constraint validation."""
        validator = EngineeringValidator()

        # Reasonable frequency
        result = validator.validate_constraint(
            constraint_type="frequency",
            value=50
        )
        assert result.valid

    def test_suggest_bounds(self):
        """Test bounds suggestion."""
        validator = EngineeringValidator()

        param_name = "thickness"
        current_value = 5.0
        suggestion = validator.suggest_bounds(param_name, current_value)

        # Returns tuple (min, max) or dict
        assert suggestion is not None
        if isinstance(suggestion, tuple):
            assert suggestion[0] < current_value
            assert suggestion[1] > current_value
        else:
            assert suggestion["min"] < current_value
            assert suggestion["max"] > current_value

    def test_detect_anti_patterns(self):
        """Test anti-pattern detection via validator."""
        validator = EngineeringValidator()
        state = InterviewState()

        state.answers["objectives"] = [{"goal": "minimize_mass"}]
        state.answers["constraints"] = []

        patterns = validator.detect_anti_patterns(state, {})
        assert len(patterns) > 0
        assert any(p.id == "mass_no_constraint" for p in patterns)

    def test_get_material(self):
        """Test getting material via validator's materials database."""
        validator = EngineeringValidator()

        mat = validator.materials_db.get_material("al_6061_t6")
        assert mat is not None
        assert mat.yield_stress is not None


class TestValidationResult:
    """Tests for ValidationResult dataclass."""

    def test_valid_result(self):
        """Test creating valid result."""
        result = ValidationResult(valid=True, message="OK")
        assert result.valid
        assert result.message == "OK"
        assert result.severity == "ok"

    def test_invalid_result(self):
        """Test creating invalid result."""
        result = ValidationResult(
            valid=False,
            message="Stress too high",
            severity="error",
            suggestion="Lower the stress limit"
        )
        assert not result.valid
        assert result.suggestion == "Lower the stress limit"

    def test_is_blocking(self):
        """Test is_blocking method."""
        blocking = ValidationResult(valid=False, message="Error", severity="error")
        assert blocking.is_blocking()

        non_blocking = ValidationResult(valid=True, message="Warning", severity="warning")
        assert not non_blocking.is_blocking()


class TestAntiPattern:
    """Tests for AntiPattern dataclass."""

    def test_anti_pattern_creation(self):
        """Test creating AntiPattern."""
        pattern = AntiPattern(
            id="test_pattern",
            name="Test Pattern",
            description="A test anti-pattern",
            severity="warning",
            fix_suggestion="Fix it"
        )

        assert pattern.id == "test_pattern"
        assert pattern.severity == "warning"
        assert not pattern.acknowledged

    def test_acknowledge_pattern(self):
        """Test acknowledging pattern."""
        pattern = AntiPattern(
            id="test",
            name="Test",
            description="Test",
            severity="error"
        )

        assert not pattern.acknowledged
        pattern.acknowledged = True
        assert pattern.acknowledged

    def test_to_dict(self):
        """Test conversion to dict."""
        pattern = AntiPattern(
            id="test",
            name="Test",
            description="Test desc",
            severity="warning",
            fix_suggestion="Do this"
        )

        d = pattern.to_dict()
        assert d["id"] == "test"
        assert d["severity"] == "warning"
        assert d["fix_suggestion"] == "Do this"

