
import pytest
from unittest.mock import MagicMock, patch
from kladml.models.base import BaseModel
from kladml.tasks import MLTask

class ConcreteModel(BaseModel):
    @property
    def ml_task(self): return MLTask.CLASSIFICATION
    def train(self, X_train, **kwargs): return {"loss": 0.1}
    def predict(self, X, **kwargs): return []
    def evaluate(self, X_test, **kwargs): return {}
    def save(self, path): pass
    def load(self, path): pass

class TestBaseModelExtended:
    """Extended tests for BaseModel concrete methods."""

    def test_save_checkpoint_default(self):
        """Test default save_checkpoint calls save()."""
        model = ConcreteModel()
        with patch.object(model, 'save') as mock_save:
            model.save_checkpoint("ckpt.pt")
            mock_save.assert_called_with("ckpt.pt")

    def test_export_model_not_implemented(self):
        """Test export_model raises NotImplementedError by default."""
        model = ConcreteModel()
        with pytest.raises(NotImplementedError):
            model.export_model("model.onnx")

    @patch("kladml.models.base.logging")
    def test_run_training_auto_export_exception(self, mock_logging):
        """Test run_training handles export exceptions gracefully."""
        model = ConcreteModel(config={"auto_export": True})
        
        # Mock export to fail
        model.export_model = MagicMock(side_effect=Exception("Export failed"))
        
        metrics = model.run_training(X_train=[])
        
        assert metrics == {"loss": 0.1}
        assert model.is_trained
        # Should verify warning was logged?
        # mock_logging.getLogger.return_value.warning.assert_called() 
        # (Logging mocking is tricky without specific setup, assumed covered by logic flow)

    @patch("kladml.training.callbacks.ProjectLogger")
    @patch("kladml.training.checkpoint.CheckpointManager")
    @patch("kladml.training.callbacks.EarlyStoppingCallback")
    @patch("kladml.training.callbacks.MetricsCallback")
    def test_init_standard_callbacks(self, mock_metrics, mock_es, mock_ckpt, mock_logger):
        """Test callback initialization logic."""
        model = ConcreteModel(config={
            "early_stopping": {"enabled": True, "patience": 3},
            "checkpoint_frequency": 2,
            "family_name": "fam"
        })
        
        # We need to manually call _init_standard_callbacks as it's usually called by runner/executor
        # But we can test it directly if we want to verify logic
        model._init_standard_callbacks("run1", "proj1", "exp1")
        
        # Verify ProjectLogger init
        mock_logger.assert_called_with(
            project_name="proj1",
            experiment_name="exp1",
            run_id="run1",
            projects_dir="./data/projects",
            family_name="fam"
        )
        
        # Verify CheckpointManager
        mock_ckpt.assert_called_with(
            project_name="proj1",
            experiment_name="exp1",
            run_id="run1",
            base_dir="./data/projects",
            checkpoint_frequency=2,
            family_name="fam"
        )
        
        # Verify EarlyStopping
        mock_es.assert_called_with(
            patience=3,
            metric="val_loss",
            mode="min",
            min_delta=0.0
        )
        
        # Verify attributes set
        assert hasattr(model, "_callbacks_list")
