"""
Pipeline Checkpoint System for Compliance Pipeline.

Provides checkpoint tracking, validation, and persistence for the compliance pipeline.
Checkpoints are saved to /tmp/pipeline_checkpoints.json and can be queried via API.
"""

import json
import os
from datetime import datetime
from dataclasses import dataclass, asdict, field
from typing import Dict, List, Optional, Any
from enum import Enum
import logging

logger = logging.getLogger(__name__)

CHECKPOINT_FILE = "/tmp/pipeline_checkpoints.json"


class CheckpointStatus(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    SKIPPED = "skipped"


class ValidationStatus(str, Enum):
    PASSED = "passed"
    WARNING = "warning"
    FAILED = "failed"
    NOT_RUN = "not_run"


@dataclass
class ValidationResult:
    """Result of a validation check."""
    name: str
    status: ValidationStatus
    expected: Any
    actual: Any
    message: str


@dataclass
class PipelineCheckpoint:
    """A checkpoint in the pipeline."""
    phase: str
    name: str
    status: CheckpointStatus
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    duration_seconds: Optional[float] = None
    metrics: Dict[str, Any] = field(default_factory=dict)
    validations: List[Dict] = field(default_factory=list)
    error: Optional[str] = None


@dataclass
class PipelineState:
    """Overall pipeline state."""
    pipeline_id: str
    status: str
    started_at: str
    completed_at: Optional[str] = None
    current_phase: Optional[str] = None
    checkpoints: List[PipelineCheckpoint] = field(default_factory=list)
    summary: Dict[str, Any] = field(default_factory=dict)


class CheckpointManager:
    """Manages pipeline checkpoints and validations."""

    def __init__(self, pipeline_id: Optional[str] = None):
        self.pipeline_id = pipeline_id or datetime.now().strftime("%Y%m%d_%H%M%S")
        self.state = PipelineState(
            pipeline_id=self.pipeline_id,
            status="initializing",
            started_at=datetime.now().isoformat(),
            checkpoints=[]
        )
        self._current_checkpoint: Optional[PipelineCheckpoint] = None
        self._checkpoint_start_time: Optional[float] = None

    def start_checkpoint(self, phase: str, name: str) -> None:
        """Start a new checkpoint."""
        import time
        self._checkpoint_start_time = time.time()
        self._current_checkpoint = PipelineCheckpoint(
            phase=phase,
            name=name,
            status=CheckpointStatus.RUNNING,
            started_at=datetime.now().isoformat(),
            metrics={},
            validations=[]
        )
        self.state.current_phase = phase
        self.state.status = "running"
        logger.info(f"[CHECKPOINT] Started: {phase} - {name}")
        self._save()

    def add_metric(self, name: str, value: Any) -> None:
        """Add a metric to the current checkpoint."""
        if self._current_checkpoint:
            self._current_checkpoint.metrics[name] = value
            self._save()

    def validate(self, name: str, expected: Any, actual: Any,
                 tolerance: float = 0.0, min_value: Optional[Any] = None) -> ValidationResult:
        """
        Validate a metric against expected value.

        Args:
            name: Validation name
            expected: Expected value (can be exact or minimum)
            actual: Actual value
            tolerance: Percentage tolerance for numeric values (0.0 = exact match)
            min_value: If set, validates that actual >= min_value
        """
        status = ValidationStatus.PASSED
        message = "OK"

        if min_value is not None:
            # Minimum value check
            if actual < min_value:
                status = ValidationStatus.FAILED
                message = f"Below minimum: {actual} < {min_value}"
            elif actual < expected:
                status = ValidationStatus.WARNING
                message = f"Below expected but above minimum: {min_value} <= {actual} < {expected}"
            else:
                message = f"OK: {actual} >= {expected}"
        elif isinstance(expected, (int, float)) and isinstance(actual, (int, float)):
            # Numeric comparison with tolerance
            if tolerance > 0:
                lower = expected * (1 - tolerance)
                upper = expected * (1 + tolerance)
                if actual < lower:
                    status = ValidationStatus.FAILED
                    message = f"Below tolerance: {actual} < {lower:.0f} (expected {expected} +/- {tolerance*100:.0f}%)"
                elif actual > upper:
                    status = ValidationStatus.WARNING
                    message = f"Above expected: {actual} > {upper:.0f}"
                else:
                    message = f"Within tolerance: {lower:.0f} <= {actual} <= {upper:.0f}"
            else:
                if actual < expected:
                    status = ValidationStatus.FAILED
                    message = f"Below expected: {actual} < {expected}"
                elif actual > expected:
                    status = ValidationStatus.WARNING
                    message = f"Above expected: {actual} > {expected}"
        else:
            # Exact match
            if actual != expected:
                status = ValidationStatus.FAILED
                message = f"Mismatch: {actual} != {expected}"

        result = ValidationResult(
            name=name,
            status=status,
            expected=expected,
            actual=actual,
            message=message
        )

        if self._current_checkpoint:
            self._current_checkpoint.validations.append(asdict(result))
            self._save()

        log_level = logging.INFO if status == ValidationStatus.PASSED else (
            logging.WARNING if status == ValidationStatus.WARNING else logging.ERROR
        )
        logger.log(log_level, f"[VALIDATION] {name}: {message}")

        return result

    def complete_checkpoint(self, success: bool = True, error: Optional[str] = None) -> None:
        """Complete the current checkpoint."""
        import time
        if self._current_checkpoint:
            self._current_checkpoint.status = CheckpointStatus.COMPLETED if success else CheckpointStatus.FAILED
            self._current_checkpoint.completed_at = datetime.now().isoformat()
            if self._checkpoint_start_time:
                self._current_checkpoint.duration_seconds = time.time() - self._checkpoint_start_time
            if error:
                self._current_checkpoint.error = error

            self.state.checkpoints.append(self._current_checkpoint)

            status_icon = "✅" if success else "❌"
            logger.info(f"[CHECKPOINT] {status_icon} Completed: {self._current_checkpoint.phase} - {self._current_checkpoint.name}")

            self._current_checkpoint = None
            self._checkpoint_start_time = None
            self._save()

    def fail_checkpoint(self, error: str) -> None:
        """Mark current checkpoint as failed."""
        self.complete_checkpoint(success=False, error=error)
        self.state.status = "failed"
        self._save()

    def complete_pipeline(self, summary: Dict[str, Any]) -> None:
        """Mark pipeline as complete."""
        self.state.status = "completed"
        self.state.completed_at = datetime.now().isoformat()
        self.state.current_phase = None
        self.state.summary = summary
        self._save()
        logger.info("[PIPELINE] Complete!")

    def get_state(self) -> Dict[str, Any]:
        """Get current pipeline state as dict."""
        return asdict(self.state)

    def _save(self) -> None:
        """Save state to file."""
        try:
            with open(CHECKPOINT_FILE, "w") as f:
                json.dump(asdict(self.state), f, indent=2, ensure_ascii=False)
        except Exception as e:
            logger.error(f"Failed to save checkpoint state: {e}")

    @staticmethod
    def load_state() -> Optional[Dict[str, Any]]:
        """Load state from file."""
        try:
            if os.path.exists(CHECKPOINT_FILE):
                with open(CHECKPOINT_FILE, "r") as f:
                    return json.load(f)
        except Exception as e:
            logger.error(f"Failed to load checkpoint state: {e}")
        return None


# Expected values for validation (can be adjusted based on experience)
EXPECTED_VALUES = {
    "ingestion": {
        "total_chunks": 11000,  # Expected minimum chunks (24 regulations)
        "min_chunks": 10000,    # Absolute minimum acceptable
        "regulations": {
            "GDPR": {"min": 600, "expected": 700},
            "AIACT": {"min": 1000, "expected": 1200},
            "CRA": {"min": 600, "expected": 700},
            "NIS2": {"min": 400, "expected": 530},
            "TDDDG": {"min": 150, "expected": 187},
            "BSI-TR-03161-1": {"min": 180, "expected": 227},
            "BSI-TR-03161-2": {"min": 170, "expected": 214},
            "BSI-TR-03161-3": {"min": 160, "expected": 199},
            "DORA": {"min": 300, "expected": 500},
            "PSD2": {"min": 400, "expected": 600},
            "AMLR": {"min": 350, "expected": 550},
            "EHDS": {"min": 400, "expected": 600},
            "MiCA": {"min": 500, "expected": 800},
        }
    },
    "extraction": {
        "total_checkpoints": 4500,
        "min_checkpoints": 4000,
        "checkpoints_per_regulation": {
            "GDPR": {"min": 150, "expected": 250},
            "AIACT": {"min": 200, "expected": 350},
            "DORA": {"min": 100, "expected": 180},
        }
    },
    "controls": {
        "total_controls": 1100,
        "min_controls": 1000,
    },
    "measures": {
        "total_measures": 1100,
        "min_measures": 1000,
    }
}
