"""
Gating Validation Service

Validates fine-tuned models before deployment using quality gates:
- Gate 1: Regression validation (mAP drop on original test set)
- Gate 2: New sample validation (detection rate on pool documents)
"""

import logging
from pathlib import Path
from uuid import UUID

from backend.data.admin_models import GatingResult
from backend.data.database import get_session_context
from backend.data.repositories.model_version_repository import ModelVersionRepository

logger = logging.getLogger(__name__)

# Gate 1 thresholds (mAP drop)
GATE1_PASS_THRESHOLD = 0.01  # < 1% drop = PASS
GATE1_REVIEW_THRESHOLD = 0.03  # 1-3% drop = REVIEW, > 3% = REJECT

# Gate 2 thresholds (detection rate)
GATE2_PASS_THRESHOLD = 0.80  # > 80% detection rate = PASS


def classify_gate1(mAP_drop: float) -> str:
    """Classify Gate 1 result based on mAP drop.

    Args:
        mAP_drop: Absolute mAP drop (positive means degradation).

    Returns:
        "pass", "review", or "reject"
    """
    if mAP_drop < GATE1_PASS_THRESHOLD:
        return "pass"
    if mAP_drop < GATE1_REVIEW_THRESHOLD:
        return "review"
    return "reject"


def classify_gate2(detection_rate: float) -> str:
    """Classify Gate 2 result based on detection rate.

    Args:
        detection_rate: Fraction of new samples correctly detected (0-1).

    Returns:
        "pass" or "review"
    """
    if detection_rate >= GATE2_PASS_THRESHOLD:
        return "pass"
    return "review"


def compute_overall_status(gate1_status: str, gate2_status: str) -> str:
    """Compute overall gating status from individual gates.

    Rules:
    - Any "reject" -> overall "reject"
    - Any "review" (and no reject) -> overall "review"
    - All "pass" -> overall "pass"
    """
    if gate1_status == "reject" or gate2_status == "reject":
        return "reject"
    if gate1_status == "review" or gate2_status == "review":
        return "review"
    return "pass"


def run_gating_validation(
    model_version_id: str | UUID,
    new_model_path: str,
    base_model_version_id: str | UUID | None,
    data_yaml: str,
    task_id: str | UUID | None = None,
) -> GatingResult:
    """Run deployment gating validation for a fine-tuned model.

    Args:
        model_version_id: The fine-tuned model version to validate.
        new_model_path: Path to the new model weights.
        base_model_version_id: The base model version to compare against.
        data_yaml: Path to the dataset YAML (for validation).
        task_id: Optional training task ID.

    Returns:
        GatingResult with gate statuses.
    """
    model_versions = ModelVersionRepository()

    # Gate 1: Regression validation
    gate1_status = "pending"
    gate1_original_mAP = None
    gate1_new_mAP = None
    gate1_mAP_drop = None

    try:
        if base_model_version_id:
            base_model = model_versions.get(str(base_model_version_id))
            if base_model and base_model.metrics_mAP is not None:
                gate1_original_mAP = base_model.metrics_mAP

                # Run validation with new model
                from shared.training import YOLOTrainer, TrainingConfig

                val_config = TrainingConfig(
                    model_path=new_model_path,
                    data_yaml=data_yaml,
                )
                trainer = YOLOTrainer(config=val_config)
                val_metrics = trainer.validate(split="val")
                gate1_new_mAP = val_metrics.get("mAP50")

                if gate1_new_mAP is not None:
                    gate1_mAP_drop = gate1_original_mAP - gate1_new_mAP
                    gate1_status = classify_gate1(gate1_mAP_drop)
                    logger.info(
                        "Gate 1: original_mAP=%.4f, new_mAP=%.4f, drop=%.4f -> %s",
                        gate1_original_mAP, gate1_new_mAP, gate1_mAP_drop, gate1_status,
                    )
                else:
                    gate1_status = "review"
                    logger.warning("Gate 1: Could not compute new mAP, marking as review")
            else:
                gate1_status = "pass"
                logger.info("Gate 1: No base model metrics available, skipping (pass)")
        else:
            gate1_status = "pass"
            logger.info("Gate 1: No base model specified, skipping (pass)")
    except Exception as e:
        gate1_status = "review"
        logger.error("Gate 1 failed: %s", e)

    # Gate 2: New sample validation
    # For now, we use the training metrics as a proxy
    # Full implementation would run inference on pool documents
    gate2_status = "pass"
    gate2_detection_rate = None
    gate2_total_samples = None
    gate2_detected_samples = None

    try:
        new_model = model_versions.get(str(model_version_id))
        if new_model and new_model.metrics_mAP is not None:
            # Use mAP as proxy for detection rate on new samples
            gate2_detection_rate = new_model.metrics_mAP
            if gate2_detection_rate is not None:
                gate2_status = classify_gate2(gate2_detection_rate)
                logger.info(
                    "Gate 2: detection_rate=%.4f -> %s",
                    gate2_detection_rate, gate2_status,
                )
    except Exception as e:
        gate2_status = "review"
        logger.error("Gate 2 failed: %s", e)

    # Compute overall status
    overall_status = compute_overall_status(gate1_status, gate2_status)
    logger.info("Gating overall: %s (gate1=%s, gate2=%s)", overall_status, gate1_status, gate2_status)

    # Save result
    with get_session_context() as session:
        result = GatingResult(
            model_version_id=UUID(str(model_version_id)),
            task_id=UUID(str(task_id)) if task_id else None,
            gate1_status=gate1_status,
            gate1_original_mAP=gate1_original_mAP,
            gate1_new_mAP=gate1_new_mAP,
            gate1_mAP_drop=gate1_mAP_drop,
            gate2_status=gate2_status,
            gate2_detection_rate=gate2_detection_rate,
            gate2_total_samples=gate2_total_samples,
            gate2_detected_samples=gate2_detected_samples,
            overall_status=overall_status,
        )
        session.add(result)
        session.commit()
        session.refresh(result)
        session.expunge(result)

    # Update model version gating status
    _update_model_gating_status(str(model_version_id), overall_status)

    return result


def _update_model_gating_status(version_id: str, status: str) -> None:
    """Update the gating_status field on a ModelVersion."""
    from backend.data.admin_models import ModelVersion

    with get_session_context() as session:
        model = session.get(ModelVersion, UUID(version_id))
        if model:
            model.gating_status = status
            session.add(model)
            session.commit()
