"""Tests for fine-tuning module — Task 50."""

from __future__ import annotations

from uuid import uuid4

from services.intelligence_pipeline_v3.fine_tuning.evaluation import (
    EvaluationResult,
    ModelCard,
    PromotionDecision,
)
from services.intelligence_pipeline_v3.fine_tuning.trainer import (
    TrainingConfig,
    TrainingRun,
    TrainingStatus,
)


class TestTrainingRun:
    """Task 50.1: Training pipeline."""

    def test_create_training_run(self):
        config = TrainingConfig(
            base_model="GLiNER2-large",
            schema_version="1.0",
            dataset_version="v1",
        )
        run = TrainingRun.create(config)
        assert run.status == TrainingStatus.PENDING
        assert run.config.base_model == "GLiNER2-large"

    def test_lifecycle(self):
        config = TrainingConfig()
        run = TrainingRun.create(config)
        run.start()
        assert run.status == TrainingStatus.PREPARING_DATA
        assert run.started_at is not None
        run.begin_training()
        assert run.status == TrainingStatus.TRAINING
        run.begin_evaluation()
        assert run.status == TrainingStatus.EVALUATING
        run.complete(
            artifact_path="/models/gliner2-ft-v1",
            model_version="gliner2-ft-v1.0",
            train_loss=0.15,
            validation_loss=0.20,
            best_epoch=7,
        )
        assert run.status == TrainingStatus.COMPLETED
        assert run.model_version == "gliner2-ft-v1.0"
        assert run.duration_seconds is not None

    def test_failure(self):
        run = TrainingRun.create(TrainingConfig())
        run.start()
        run.fail("OOM error during training")
        assert run.status == TrainingStatus.FAILED
        assert "OOM" in run.errors[0]


class TestEvaluation:
    """Task 50.2: Holdout evaluation and promotion gates."""

    def test_evaluation_passes_correctness_gates(self):
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-v1.0",
            entity_f1=0.92,
            event_f1=0.85,
            entity_f1_delta=0.02,
            event_f1_delta=0.01,
            calibration_ece=0.05,
        )
        assert result.passes_correctness_gates()

    def test_evaluation_fails_on_entity_regression(self):
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-bad",
            entity_f1=0.80,
            entity_f1_delta=-0.05,  # Regression
            calibration_ece=0.05,
        )
        assert not result.passes_correctness_gates()

    def test_evaluation_fails_on_high_calibration(self):
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-uncalibrated",
            entity_f1=0.95,
            entity_f1_delta=0.05,
            calibration_ece=0.15,  # Too high
        )
        assert not result.passes_correctness_gates()

    def test_promotion_not_based_on_adjudication_rate(self):
        """Task 50.4: Promoted only when correctness gates pass,
        not merely when adjudication rate falls.
        """
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-fewer-adj",
            entity_f1=0.80,
            entity_f1_delta=-0.05,  # Regression!
            event_f1_delta=-0.03,  # Regression!
            calibration_ece=0.10,  # Too high!
            adjudication_rate_before=0.40,
            adjudication_rate_after=0.15,  # Great improvement
            adjudication_rate_delta=-0.25,
        )
        # Despite great adjudication improvement, correctness fails
        assert result.promotion_decision() == PromotionDecision.REJECT

    def test_promote_when_all_gates_pass(self):
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-good",
            entity_f1=0.94,
            entity_f1_delta=0.02,
            event_f1=0.88,
            event_f1_delta=0.01,
            calibration_ece=0.04,
            adjudication_rate_delta=-0.10,
        )
        assert result.promotion_decision() == PromotionDecision.PROMOTE

    def test_needs_review_on_adjudication_increase(self):
        result = EvaluationResult.create(
            training_run_id=uuid4(),
            model_version="gliner2-ft-weird",
            entity_f1=0.94,
            entity_f1_delta=0.02,
            event_f1_delta=0.01,
            calibration_ece=0.04,
            adjudication_rate_delta=0.10,  # Adjudication increased a lot
        )
        assert result.promotion_decision() == PromotionDecision.NEEDS_REVIEW


class TestModelCard:
    """Task 50: Model card with training metadata."""

    def test_create_model_card(self):
        card = ModelCard.create(
            model_version="gliner2-ft-v1.0",
            base_model="GLiNER2-large",
            training_run_id=uuid4(),
            training_range="2024-01 to 2024-06",
            dataset_version="corpus-v1",
        )
        assert card.model_version == "gliner2-ft-v1.0"
        assert card.base_model == "GLiNER2-large"
        assert not card.promoted
        assert not card.deprecated

    def test_promote_and_deprecate(self):
        card = ModelCard.create(
            model_version="gliner2-ft-v1.0",
            base_model="GLiNER2-large",
            training_run_id=uuid4(),
        )
        card.promote()
        assert card.promoted
        assert card.promoted_at is not None
        card.deprecate()
        assert card.deprecated

    def test_model_card_has_required_fields(self):
        """Requirement 17.6: Model cards must include specific fields."""
        card = ModelCard.create(
            model_version="v1",
            base_model="GLiNER2",
            training_run_id=uuid4(),
            training_range="2024-01 to 2024-06",
            dataset_version="v1",
            schema_version="1.0",
            entity_types=["company", "event"],
        )
        d = card.to_dict()
        assert "training_range" in d
        assert "dataset_version" in d
        assert "intended_use" in d
        assert "limitations" in d
        assert "entity_types" in d
