import json
import tempfile
import typing
from pathlib import Path

import pytest

from autotrainer.training import TrainingPhase, TrainingPlan, TrainingProgressState
from autotrainer.training.training_action import ReachDistanceAction


@pytest.fixture
def sample_phase_data():
    """Sample phase data dictionary"""
    return {
        "phase_id": "test-phase-uuid-12345",
        "name": "test_phase",
        "description": "A test phase",
        "fallback_predicate": None,
        "advance_predicate": None,
        "session_actions": [],
        "is_pellet_delivery_enabled": False,
        "is_pellet_cover_enabled": True,
        "starting_baseline_intensity": 15,
        "is_auto_clamp_enabled": False
    }


@pytest.fixture
def temp_json_file():
    """Create a temporary JSON file for testing"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
        temp_path = Path(f.name)
    yield temp_path
    if temp_path.exists():
        temp_path.unlink()


class TestTrainingPhase:
    """Test TrainingPhase serialization and deserialization"""

    def test_training_phase_serialization(self, sample_phase_data):
        """Test TrainingPhase to_dict() includes all properties"""
        phase = TrainingPhase(sample_phase_data["phase_id"])
        phase.name = sample_phase_data["name"]
        phase.description = sample_phase_data["description"]
        phase.fallback_predicate = sample_phase_data["fallback_predicate"]
        phase.advance_predicate = sample_phase_data["advance_predicate"]
        phase.session_actions = sample_phase_data["session_actions"]
        phase.is_pellet_delivery_enabled = sample_phase_data["is_pellet_delivery_enabled"]
        phase.is_pellet_cover_enabled = sample_phase_data["is_pellet_cover_enabled"]
        phase.starting_baseline_intensity = sample_phase_data["starting_baseline_intensity"]
        phase.is_auto_clamp_enabled = sample_phase_data["is_auto_clamp_enabled"]

        result = phase.to_dict()

        assert result["phaseId"] == sample_phase_data["phase_id"]
        assert result["name"] == sample_phase_data["name"]
        assert result["description"] == sample_phase_data["description"]
        assert result["fallbackPredicate"] == sample_phase_data["fallback_predicate"]
        assert result["advancePredicate"] == sample_phase_data["advance_predicate"]
        assert result["sessionActions"] == sample_phase_data["session_actions"]
        assert result["isPelletDeliveryEnabled"] == sample_phase_data["is_pellet_delivery_enabled"]
        assert result["isPelletCoverEnabled"] == sample_phase_data["is_pellet_cover_enabled"]
        assert result["startingBaselineIntensity"] == sample_phase_data["starting_baseline_intensity"]
        assert result["isAutoClampEnabled"] == sample_phase_data["is_auto_clamp_enabled"]

    def test_training_phase_deserialization(self, sample_phase_data):
        """Test TrainingPhase from_dict() restores all properties"""
        phase = TrainingPhase.from_dict(sample_phase_data)

        assert phase.phase_id == sample_phase_data["phase_id"]
        assert phase.name == sample_phase_data["name"]
        assert phase.description == sample_phase_data["description"]
        assert phase.fallback_predicate == sample_phase_data["fallback_predicate"]
        assert phase.advance_predicate == sample_phase_data["advance_predicate"]
        assert phase.session_actions == sample_phase_data["session_actions"]
        assert phase.is_pellet_delivery_enabled == sample_phase_data["is_pellet_delivery_enabled"]
        assert phase.is_pellet_cover_enabled == sample_phase_data["is_pellet_cover_enabled"]
        assert phase.starting_baseline_intensity == sample_phase_data["starting_baseline_intensity"]
        assert phase.is_auto_clamp_enabled == sample_phase_data["is_auto_clamp_enabled"]

    def test_training_phase_backward_compatibility(self):
        """Test TrainingPhase handles missing optional fields with defaults"""
        minimal_data = {
            "phase_id": "test-phase-uuid-12345",
            "name": "test_phase",
            "description": "A test phase"
        }

        phase = TrainingPhase.from_dict(minimal_data)

        assert phase.phase_id == minimal_data["phase_id"]
        assert phase.name == minimal_data["name"]
        assert phase.description == minimal_data["description"]
        assert phase.fallback_predicate is None
        assert phase.advance_predicate is None
        assert phase.session_actions == []
        assert phase.starting_baseline_intensity == 5
        assert phase.is_pellet_delivery_enabled is False
        assert phase.is_pellet_cover_enabled is False
        assert phase.is_auto_clamp_enabled is False


class TestTrainingPlan:
    """Test TrainingPlan serialization and deserialization"""

    @pytest.fixture
    def test_json_file_path(self):
        """Path to test JSON file"""
        return Path(__file__).parent / "mocks" / "training_plan_serialization.json"

    @pytest.fixture
    def progress_test_json_file_path(self):
        """Path to test JSON file"""
        return Path(__file__).parent / "mocks" / "training_progress_serialization.json"

    def test_training_plan_from_json_file(self, test_json_file_path):
        """Test loading TrainingPlan from JSON file"""
        plan = TrainingPlan.from_json_file(test_json_file_path)

        assert plan.plan_id == "test-plan-uuid-12345"
        assert plan.name == "Test Training Plan"
        assert plan.description == "A test training plan with multiple phases"
        assert len(plan.phases) == 3

        phase1 = plan.phases[0]
        assert phase1.phase_id == "phase-1-uuid-67890"
        assert phase1.name == "initial_phase"
        assert phase1.advance_predicate is not None
        assert phase1.fallback_predicate is None
        assert len(phase1.session_actions) == 1
        assert isinstance(phase1.session_actions[0], ReachDistanceAction)
        action = typing.cast(ReachDistanceAction, phase1.session_actions[0])
        assert action.distance == pytest.approx(3.0)
        assert phase1.is_pellet_delivery_enabled is True
        assert phase1.is_pellet_cover_enabled is True
        assert phase1.starting_baseline_intensity == 10
        assert phase1.is_auto_clamp_enabled is False

        assert plan.machine is not None
        assert len(plan._phase_map) == 3
        assert "initial_phase" in plan._phase_map

    def test_training_plan_to_dict(self, test_json_file_path):
        """Test TrainingPlan serialization to dictionary"""
        plan = TrainingPlan.from_json_file(test_json_file_path)
        result = plan.to_dict()

        assert result["planId"] == "test-plan-uuid-12345"
        assert result["name"] == "Test Training Plan"
        assert result["description"] == "A test training plan with multiple phases"
        assert len(result["phases"]) == 3
        assert result["phases"][0]["name"] == "initial_phase"

    def test_training_plan_from_dict(self, test_json_file_path):
        """Test TrainingPlan deserialization from dictionary"""
        with open(test_json_file_path) as f:
            data = json.load(f)

        plan = TrainingPlan.from_dict(data)

        assert plan.plan_id == data["planId"]
        assert plan.name == data["name"]
        assert plan.description == data["description"]
        assert len(plan.phases) == len(data["phases"])

    def test_training_plan_progress_serialization(self, test_json_file_path):
        """Test TrainingPlan progress serialization"""
        plan = TrainingPlan.from_json_file(test_json_file_path)

        progress_dict = plan.serialize_progress()

        assert progress_dict["plan_id"] == plan.plan_id
        assert progress_dict["progress_state"] == TrainingProgressState.Active

        # Individual TrainingProgress content is tested in training_progress_serialization.py.  Just checking the list
        # is correct.
        progress_data = progress_dict["progress"]
        assert len(progress_data) == 3

    def test_training_plan_progress_deserialization(self, test_json_file_path, progress_test_json_file_path):
        """Test TrainingPlan progress deserialization"""
        plan = TrainingPlan.from_json_file(test_json_file_path)

        plan.deserialize_progress_from_file(progress_test_json_file_path)

    def test_training_plan_progress_validation(self, test_json_file_path):
        """Test progress data validation"""
        plan = TrainingPlan.from_json_file(test_json_file_path)

        missing_plan_id = {"progress_state": plan.progress_state, "progress": [
            {"timestamp": "2024-01-01T00:00:00"}]}

        with pytest.raises(KeyError):
            plan.deserialize_progress(missing_plan_id)

        wrong_plan_id = {"plan_id": "not matching", "progress_state": plan.progress_state, "progress": [
            {"timestamp": "2024-01-01T00:00:00"}]}

        with pytest.raises(ValueError):
            plan.deserialize_progress(wrong_plan_id)

        missing_progress_state = {"plan_id": plan.plan_id, "progress": [
            {"timestamp": "2024-01-01T00:00:00"}]}

        with pytest.raises(KeyError):
            plan.deserialize_progress(missing_progress_state)

        wrong_length_data = {"plan_id": plan.plan_id, "progress_state": plan.progress_state, "progress": [
            {"timestamp": "2024-01-01T00:00:00"}]}

        with pytest.raises(ValueError):
            plan.deserialize_progress(wrong_length_data)

        invalid_data = {"progress": [
            {"timestamp": "2024-01-01T00:00:00"},
            "not_a_dict",
            {"timestamp": "2024-01-01T00:00:00"}
        ]}

        with pytest.raises(KeyError):
            plan.deserialize_progress(invalid_data)


class TestIntegration:
    """Integration tests for complete serialization workflows"""

    @pytest.fixture
    def test_json_file_path(self):
        """Path to test JSON file"""
        return Path(__file__).parent / "mocks" / "training_plan_serialization.json"

    def test_round_trip_serialization(self, test_json_file_path, temp_json_file):
        """Test complete round-trip: load -> modify -> serialize -> deserialize -> verify"""
        original_plan = TrainingPlan.from_json_file(test_json_file_path)

        original_plan.name = "Modified Plan"
        original_plan.phases[0].description = "Modified phase description"

        plan_dict = original_plan.to_dict()
        restored_plan = TrainingPlan.from_dict(plan_dict)

        assert restored_plan.name == "Modified Plan"
        assert restored_plan.phases[0].description == "Modified phase description"

        assert restored_plan.plan_id == original_plan.plan_id
        assert len(restored_plan.phases) == len(original_plan.phases)
        assert restored_plan.phases[1].phase_id == original_plan.phases[1].phase_id

    def test_state_machine_initialization(self, test_json_file_path):
        """Test that state machine is properly initialized after deserialization"""
        plan = TrainingPlan.from_json_file(test_json_file_path)

        assert plan.machine is not None
        assert plan.state == "initial_phase"

        assert hasattr(plan, "advance_to_intermediate_phase")
        assert hasattr(plan, "advance_to_advanced_phase")
        assert hasattr(plan, "fallback_to_initial_phase")

        assert len(plan._phase_map) == 3
        assert "initial_phase" in plan._phase_map
        assert "intermediate_phase" in plan._phase_map
        assert "advanced_phase" in plan._phase_map

        current_phase = plan.current_phase
        assert current_phase is not None
        assert current_phase.name == "initial_phase"

    def test_system_context_integration(self, test_json_file_path):
        """Test that SystemContext allows None values and is properly created"""

        plan = TrainingPlan.from_json_file(test_json_file_path)

        assert plan._system_context.algorithm is None
        assert plan._system_context.pellet_device is None
        assert plan._system_context.tunnel_device is None

        current_phase = plan.current_phase
        assert current_phase is not None

        current_phase.before_enter(plan._system_context)
        current_phase.perform_session_actions(plan._system_context)
        result1 = current_phase.should_fallback(plan._system_context)
        result2 = current_phase.should_advance(plan._system_context)

        assert result1 is False
        assert result2 is False

    def test_comprehensive_field_preservation(self, test_json_file_path):
        """Test that all fields are preserved through complete serialization cycle"""
        original_plan = TrainingPlan.from_json_file(test_json_file_path)

        plan_dict = original_plan.to_dict()

        restored_plan = TrainingPlan.from_dict(plan_dict)

        assert restored_plan.plan_id == original_plan.plan_id
        assert restored_plan.name == original_plan.name
        assert restored_plan.description == original_plan.description
        assert len(restored_plan.phases) == len(original_plan.phases)

        for i, (original_phase, restored_phase) in enumerate(zip(original_plan.phases, restored_plan.phases)):
            assert restored_phase.phase_id == original_phase.phase_id, f"Phase {i} ID mismatch"
            assert restored_phase.name == original_phase.name, f"Phase {i} name mismatch"
            assert restored_phase.description == original_phase.description, f"Phase {i} description mismatch"
            assert restored_phase.starting_baseline_intensity == original_phase.starting_baseline_intensity, \
                f"Phase {i} baseline mismatch"
            assert restored_phase.is_pellet_cover_enabled == original_phase.is_pellet_cover_enabled, (f"Phase {i} "
                                                                                                      f"cover enabled"
                                                                                                      f" mismatch")
            assert restored_phase.is_auto_clamp_enabled == original_phase.is_auto_clamp_enabled, (f"Phase {i} clamp "
                                                                                                  f"enabled mismatch")
