"""
SAGE Escalation Workflow

Manages the escalation of queries that require human expert review.
This is a safety-critical module requiring 100% test coverage.

Escalation Triggers:
1. Novel configuration detection (no similar cases in knowledge base)
2. Safety basis change detection (query affects existing safety analysis)
3. Regulatory implication flagging (response may have regulatory impact)
4. Low confidence + high stakes (uncertain answer on critical topic)
5. Conflicting reasoning paths (self-consistency check fails)
"""

import logging
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any

logger = logging.getLogger(__name__)


class EscalationTrigger(str, Enum):
    """Conditions that trigger escalation to human review."""

    NOVEL_CONFIGURATION = "novel_configuration"
    SAFETY_BASIS_CHANGE = "safety_basis_change"
    REGULATORY_IMPLICATIONS = "regulatory_implications"
    LOW_CONFIDENCE_HIGH_STAKES = "low_confidence_high_stakes"
    CONFLICTING_REASONING = "conflicting_reasoning"


class EscalationPriority(str, Enum):
    """Priority levels for escalation requests."""

    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class EscalationStatus(str, Enum):
    """Status of an escalation request."""

    PENDING = "pending"  # Awaiting review
    IN_REVIEW = "in_review"  # Currently being reviewed
    APPROVED = "approved"  # Response approved for release
    REJECTED = "rejected"  # Response rejected, user notified
    MODIFIED = "modified"  # Response modified and approved
    EXPIRED = "expired"  # Review window expired


@dataclass
class EscalationRequest:
    """An escalation request for human review."""

    # Identification
    request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: datetime = field(default_factory=lambda: datetime.now(UTC))

    # Query details
    query: str = ""
    proposed_response: str = ""
    context: dict[str, Any] = field(default_factory=dict)

    # Escalation metadata
    triggers: list[EscalationTrigger] = field(default_factory=list)
    priority: EscalationPriority = EscalationPriority.MEDIUM
    status: EscalationStatus = EscalationStatus.PENDING

    # Review details
    assigned_reviewer: str | None = None
    reviewer_notes: str | None = None
    modified_response: str | None = None
    reviewed_at: datetime | None = None
    reviewed_by: str | None = None

    # User notification
    user_id: str | None = None
    user_notified: bool = False

    # Response hold
    response_held: bool = True  # Response held by default

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            "request_id": self.request_id,
            "created_at": self.created_at.isoformat(),
            "query": self.query,
            "proposed_response": self.proposed_response[:500],  # Truncate for summary
            "triggers": [t.value for t in self.triggers],
            "priority": self.priority.value,
            "status": self.status.value,
            "assigned_reviewer": self.assigned_reviewer,
            "reviewed_at": self.reviewed_at.isoformat() if self.reviewed_at else None,
            "reviewed_by": self.reviewed_by,
            "user_id": self.user_id,
            "response_held": self.response_held,
        }


@dataclass
class EscalationConfig:
    """Configuration for escalation workflow."""

    # Confidence thresholds
    low_confidence_threshold: float = 0.6

    # High-stakes keywords
    high_stakes_keywords: list[str] = field(default_factory=lambda: [
        "safety basis", "license", "regulatory", "compliance",
        "10 cfr", "violation", "incident", "accident", "criticality event",
        "operation", "operational", "procedure change", "new configuration",
    ])

    # Novel configuration indicators
    novelty_indicators: list[str] = field(default_factory=lambda: [
        "new", "novel", "proposed", "different from", "modified",
        "alternative", "updated", "changed", "revised", "prototype",
    ])

    # Regulatory keywords
    regulatory_keywords: list[str] = field(default_factory=lambda: [
        "10 cfr", "nrc", "doe order", "regulatory", "license",
        "compliance", "violation", "inspection", "audit",
        "documented safety analysis", "dsa", "nuclear regulatory",
    ])

    # Safety basis keywords
    safety_basis_keywords: list[str] = field(default_factory=lambda: [
        "safety basis", "safety analysis", "documented safety",
        "control", "limit", "boundary", "credited", "assumption",
        "bounding analysis", "safety evaluation", "usr", "tsr",
    ])

    # Priority mapping (number of triggers to priority)
    priority_thresholds: dict[int, EscalationPriority] = field(default_factory=lambda: {
        1: EscalationPriority.MEDIUM,
        2: EscalationPriority.HIGH,
        3: EscalationPriority.CRITICAL,
    })

    # SLA times in hours
    sla_hours: dict[EscalationPriority, int] = field(default_factory=lambda: {
        EscalationPriority.LOW: 72,
        EscalationPriority.MEDIUM: 24,
        EscalationPriority.HIGH: 8,
        EscalationPriority.CRITICAL: 2,
    })


class NotificationHandler(ABC):
    """Abstract interface for notification systems."""

    @abstractmethod
    def notify_reviewer(
        self,
        request: EscalationRequest,
        reviewer_email: str,
    ) -> bool:
        """Notify a reviewer of a new escalation."""
        pass

    @abstractmethod
    def notify_user_pending(
        self,
        request: EscalationRequest,
    ) -> bool:
        """Notify user their query is pending expert review."""
        pass

    @abstractmethod
    def notify_user_complete(
        self,
        request: EscalationRequest,
        approved: bool,
        response: str | None = None,
    ) -> bool:
        """Notify user that review is complete."""
        pass


class LoggingNotificationHandler(NotificationHandler):
    """Simple notification handler that logs notifications.

    Useful for testing and development. Production should use
    email, Slack, or other notification system.
    """

    def notify_reviewer(
        self,
        request: EscalationRequest,
        reviewer_email: str,
    ) -> bool:
        """Log reviewer notification."""
        logger.info(
            f"NOTIFICATION: Reviewer {reviewer_email} - "
            f"New escalation {request.request_id} "
            f"(priority: {request.priority.value})"
        )
        return True

    def notify_user_pending(self, request: EscalationRequest) -> bool:
        """Log user pending notification."""
        logger.info(
            f"NOTIFICATION: User {request.user_id} - "
            f"Query pending expert review (ID: {request.request_id})"
        )
        return True

    def notify_user_complete(
        self,
        request: EscalationRequest,
        approved: bool,
        response: str | None = None,
    ) -> bool:
        """Log user completion notification."""
        status = "approved" if approved else "requires revision"
        logger.info(
            f"NOTIFICATION: User {request.user_id} - "
            f"Review complete, response {status} (ID: {request.request_id})"
        )
        return True


class EscalationQueue:
    """In-memory queue for escalation requests.

    This is the synchronous in-memory implementation for backward compatibility.
    For production use with PostgreSQL, use EscalationQueueWithRepository with
    the repository pattern from escalation_repository.py.

    See escalation_repository.py for:
    - EscalationRepository (abstract base class)
    - InMemoryEscalationRepository (async in-memory implementation)
    - PostgresEscalationRepository (PostgreSQL implementation)

    Example with repository:
        from sage.core.escalation_repository import PostgresEscalationRepository

        repo = PostgresEscalationRepository(connection_string)
        queue = EscalationQueueWithRepository(repo)
        workflow = EscalationWorkflow(queue=queue)
    """

    def __init__(self) -> None:
        """Initialize the queue."""
        self._requests: dict[str, EscalationRequest] = {}

    def add(self, request: EscalationRequest) -> str:
        """Add a request to the queue."""
        self._requests[request.request_id] = request
        logger.info(f"Added escalation request {request.request_id} to queue")
        return request.request_id

    def get(self, request_id: str) -> EscalationRequest | None:
        """Get a request by ID."""
        return self._requests.get(request_id)

    def update(self, request: EscalationRequest) -> bool:
        """Update a request in the queue."""
        if request.request_id in self._requests:
            self._requests[request.request_id] = request
            return True
        return False

    def remove(self, request_id: str) -> bool:
        """Remove a request from the queue."""
        if request_id in self._requests:
            del self._requests[request_id]
            return True
        return False

    def get_pending(self) -> list[EscalationRequest]:
        """Get all pending requests."""
        return [
            r for r in self._requests.values()
            if r.status == EscalationStatus.PENDING
        ]

    def get_by_priority(
        self, priority: EscalationPriority
    ) -> list[EscalationRequest]:
        """Get requests by priority."""
        return [
            r for r in self._requests.values()
            if r.priority == priority and r.status == EscalationStatus.PENDING
        ]

    def get_by_user(self, user_id: str) -> list[EscalationRequest]:
        """Get requests by user ID."""
        return [
            r for r in self._requests.values()
            if r.user_id == user_id
        ]

    def count_pending(self) -> int:
        """Count pending requests."""
        return len(self.get_pending())

    def clear(self) -> None:
        """Clear all requests (for testing)."""
        self._requests.clear()


class EscalationQueueWithRepository:
    """Adapter that wraps an async repository for use with EscalationWorkflow.

    This adapter provides a synchronous interface to an async repository,
    allowing the existing EscalationWorkflow to use a database backend.

    Example:
        from sage.core.escalation_repository import PostgresEscalationRepository

        repo = PostgresEscalationRepository(connection_string)
        queue = EscalationQueueWithRepository(repo)
        workflow = EscalationWorkflow(queue=queue)
    """

    def __init__(self, repository: Any) -> None:
        """Initialize with a repository.

        Args:
            repository: An EscalationRepository implementation.
        """
        self._repository = repository
        import asyncio
        self._loop: Any = None

    def _get_loop(self) -> Any:
        """Get or create event loop."""
        import asyncio
        if self._loop is None or self._loop.is_closed():
            try:
                self._loop = asyncio.get_event_loop()
            except RuntimeError:
                self._loop = asyncio.new_event_loop()
                asyncio.set_event_loop(self._loop)
        return self._loop

    def _run_async(self, coro: Any) -> Any:
        """Run an async coroutine synchronously."""
        loop = self._get_loop()
        return loop.run_until_complete(coro)

    def add(self, request: EscalationRequest) -> str:
        """Add a request to the repository."""
        return self._run_async(self._repository.add(request))

    def get(self, request_id: str) -> EscalationRequest | None:
        """Get a request by ID."""
        return self._run_async(self._repository.get(request_id))

    def update(self, request: EscalationRequest) -> bool:
        """Update a request."""
        return self._run_async(self._repository.update(request))

    def remove(self, request_id: str) -> bool:
        """Remove a request."""
        return self._run_async(self._repository.remove(request_id))

    def get_pending(self) -> list[EscalationRequest]:
        """Get all pending requests."""
        return self._run_async(self._repository.get_pending())

    def get_by_priority(
        self, priority: EscalationPriority
    ) -> list[EscalationRequest]:
        """Get requests by priority."""
        return self._run_async(self._repository.get_by_priority(priority))

    def get_by_user(self, user_id: str) -> list[EscalationRequest]:
        """Get requests by user ID."""
        return self._run_async(self._repository.get_by_user(user_id))

    def count_pending(self) -> int:
        """Count pending requests."""
        return self._run_async(self._repository.count_pending())

    def clear(self) -> None:
        """Clear all requests (if supported by repository)."""
        if hasattr(self._repository, 'clear'):
            self._run_async(self._repository.clear())


class EscalationWorkflow:
    """
    Manages the complete escalation workflow.

    Handles:
    - Evaluating queries for escalation triggers
    - Creating and tracking escalation requests
    - Managing the review process
    - Releasing or rejecting responses

    Example:
        workflow = EscalationWorkflow()

        # Check if escalation needed
        result = workflow.evaluate(
            query="We're proposing a new configuration...",
            response="Based on the analysis...",
            confidence=0.5,
            context={"affects_safety_basis": True}
        )

        if result.triggers:
            # Create escalation
            request_id = workflow.create_escalation(
                query=query,
                response=response,
                triggers=result.triggers,
                user_id="user123"
            )

            # Response is held until review
            # Later, reviewer approves:
            workflow.approve(request_id, reviewer_id="reviewer456")
    """

    def __init__(
        self,
        config: EscalationConfig | None = None,
        queue: EscalationQueue | None = None,
        notification_handler: NotificationHandler | None = None,
    ):
        """Initialize escalation workflow.

        Args:
            config: Optional configuration
            queue: Optional custom queue (uses in-memory by default)
            notification_handler: Optional notification handler
        """
        self.config = config or EscalationConfig()
        self.queue = queue or EscalationQueue()
        self.notifier = notification_handler or LoggingNotificationHandler()

    def evaluate(
        self,
        query: str,
        response: str,
        confidence: float,
        context: dict[str, Any] | None = None,
        reasoning_paths: list[dict[str, Any]] | None = None,
    ) -> "EscalationEvaluation":
        """
        Evaluate whether a query/response needs escalation.

        Args:
            query: User's question
            response: Proposed response
            confidence: Confidence score (0-1)
            context: Optional context (affects_safety_basis, etc.)
            reasoning_paths: Optional multiple reasoning attempts for self-consistency

        Returns:
            EscalationEvaluation with triggers and priority
        """
        context = context or {}
        triggers: list[EscalationTrigger] = []

        # Check each escalation condition
        if self._check_novel_configuration(query, context):
            triggers.append(EscalationTrigger.NOVEL_CONFIGURATION)

        if self._check_safety_basis_change(query, context):
            triggers.append(EscalationTrigger.SAFETY_BASIS_CHANGE)

        if self._check_regulatory_implications(query, response):
            triggers.append(EscalationTrigger.REGULATORY_IMPLICATIONS)

        if self._check_low_confidence_high_stakes(query, confidence):
            triggers.append(EscalationTrigger.LOW_CONFIDENCE_HIGH_STAKES)

        if reasoning_paths and self._check_conflicting_reasoning(reasoning_paths):
            triggers.append(EscalationTrigger.CONFLICTING_REASONING)

        # Determine priority
        priority = self._determine_priority(triggers)

        return EscalationEvaluation(
            should_escalate=len(triggers) > 0,
            triggers=triggers,
            priority=priority,
        )

    def create_escalation(
        self,
        query: str,
        response: str,
        triggers: list[EscalationTrigger],
        user_id: str | None = None,
        context: dict[str, Any] | None = None,
    ) -> str:
        """
        Create an escalation request.

        Args:
            query: User's question
            response: Proposed response (will be held)
            triggers: Escalation triggers that fired
            user_id: Optional user identifier
            context: Optional additional context

        Returns:
            Request ID
        """
        priority = self._determine_priority(triggers)

        request = EscalationRequest(
            query=query,
            proposed_response=response,
            context=context or {},
            triggers=triggers,
            priority=priority,
            user_id=user_id,
            response_held=True,
        )

        request_id = self.queue.add(request)

        # Notify user that response is pending review
        if user_id:
            self.notifier.notify_user_pending(request)

        logger.info(
            f"Created escalation {request_id} with priority {priority.value}, "
            f"triggers: {[t.value for t in triggers]}"
        )

        return request_id

    def assign_reviewer(
        self,
        request_id: str,
        reviewer_id: str,
        reviewer_email: str | None = None,
    ) -> bool:
        """Assign a reviewer to an escalation request."""
        request = self.queue.get(request_id)
        if not request:
            return False

        request.assigned_reviewer = reviewer_id
        request.status = EscalationStatus.IN_REVIEW
        self.queue.update(request)

        if reviewer_email:
            self.notifier.notify_reviewer(request, reviewer_email)

        return True

    def approve(
        self,
        request_id: str,
        reviewer_id: str,
        notes: str | None = None,
    ) -> str | None:
        """
        Approve an escalation request and release response.

        Args:
            request_id: Request ID
            reviewer_id: ID of approving reviewer
            notes: Optional reviewer notes

        Returns:
            The approved response, or None if request not found
        """
        request = self.queue.get(request_id)
        if not request:
            return None

        request.status = EscalationStatus.APPROVED
        request.reviewed_by = reviewer_id
        request.reviewed_at = datetime.now(UTC)
        request.reviewer_notes = notes
        request.response_held = False
        self.queue.update(request)

        # Notify user
        if request.user_id:
            self.notifier.notify_user_complete(
                request, approved=True, response=request.proposed_response
            )

        logger.info(f"Escalation {request_id} approved by {reviewer_id}")
        return request.proposed_response

    def approve_with_modifications(
        self,
        request_id: str,
        reviewer_id: str,
        modified_response: str,
        notes: str | None = None,
    ) -> str | None:
        """
        Approve with modifications and release modified response.

        Args:
            request_id: Request ID
            reviewer_id: ID of approving reviewer
            modified_response: The modified response to release
            notes: Optional reviewer notes

        Returns:
            The modified response, or None if request not found
        """
        request = self.queue.get(request_id)
        if not request:
            return None

        request.status = EscalationStatus.MODIFIED
        request.reviewed_by = reviewer_id
        request.reviewed_at = datetime.now(UTC)
        request.reviewer_notes = notes
        request.modified_response = modified_response
        request.response_held = False
        self.queue.update(request)

        # Notify user
        if request.user_id:
            self.notifier.notify_user_complete(
                request, approved=True, response=modified_response
            )

        logger.info(f"Escalation {request_id} approved with modifications by {reviewer_id}")
        return modified_response

    def reject(
        self,
        request_id: str,
        reviewer_id: str,
        reason: str,
    ) -> bool:
        """
        Reject an escalation request.

        Args:
            request_id: Request ID
            reviewer_id: ID of rejecting reviewer
            reason: Reason for rejection

        Returns:
            True if rejection successful
        """
        request = self.queue.get(request_id)
        if not request:
            return False

        request.status = EscalationStatus.REJECTED
        request.reviewed_by = reviewer_id
        request.reviewed_at = datetime.now(UTC)
        request.reviewer_notes = reason
        request.response_held = True  # Keep held
        request.user_notified = True
        self.queue.update(request)

        # Notify user
        if request.user_id:
            self.notifier.notify_user_complete(request, approved=False)

        logger.info(f"Escalation {request_id} rejected by {reviewer_id}: {reason}")
        return True

    def get_response_if_released(
        self, request_id: str
    ) -> str | None:
        """
        Get response if it has been released (approved/modified).

        Args:
            request_id: Request ID

        Returns:
            Response if released, None otherwise
        """
        request = self.queue.get(request_id)
        if not request or request.response_held:
            return None

        if request.status == EscalationStatus.MODIFIED:
            return request.modified_response
        elif request.status == EscalationStatus.APPROVED:
            return request.proposed_response

        return None

    def is_response_held(self, request_id: str) -> bool:
        """Check if response is still held pending review."""
        request = self.queue.get(request_id)
        return request.response_held if request else False

    def _check_novel_configuration(
        self, query: str, context: dict[str, Any]
    ) -> bool:
        """Check if query involves a novel configuration."""
        query_lower = query.lower()

        # Check context flag
        if context.get("novel_configuration"):
            return True

        # Check for novelty indicators
        for indicator in self.config.novelty_indicators:
            if indicator.lower() in query_lower:
                # Also check for configuration-related keywords
                config_keywords = ["configuration", "storage", "arrangement", "geometry"]
                if any(kw in query_lower for kw in config_keywords):
                    return True

        return False

    def _check_safety_basis_change(
        self, query: str, context: dict[str, Any]
    ) -> bool:
        """Check if query affects safety basis."""
        # Check context flag
        if context.get("affects_safety_basis"):
            return True

        query_lower = query.lower()

        # Check for safety basis keywords
        for keyword in self.config.safety_basis_keywords:
            if keyword.lower() in query_lower:
                # Also check for change indicators
                change_words = ["change", "modify", "update", "revise", "differ", "new"]
                if any(cw in query_lower for cw in change_words):
                    return True

        return False

    def _check_regulatory_implications(
        self, query: str, response: str
    ) -> bool:
        """Check for regulatory implications."""
        combined = (query + " " + response).lower()

        # Count regulatory keyword matches
        matches = sum(
            1 for keyword in self.config.regulatory_keywords
            if keyword.lower() in combined
        )

        # Trigger if multiple regulatory keywords found
        return matches >= 2

    def _check_low_confidence_high_stakes(
        self, query: str, confidence: float
    ) -> bool:
        """Check for low confidence on high-stakes query."""
        if confidence >= self.config.low_confidence_threshold:
            return False

        query_lower = query.lower()

        # Check for high-stakes keywords
        for keyword in self.config.high_stakes_keywords:
            if keyword.lower() in query_lower:
                logger.info(
                    f"Low confidence ({confidence:.2f}) on high-stakes query "
                    f"(keyword: {keyword})"
                )
                return True

        return False

    def _check_conflicting_reasoning(
        self, reasoning_paths: list[dict[str, Any]]
    ) -> bool:
        """Check if multiple reasoning paths conflict."""
        if len(reasoning_paths) < 2:
            return False

        # Extract conclusions from each path
        conclusions = []
        for path in reasoning_paths:
            conclusion = path.get("conclusion", "")
            conclusions.append(conclusion.lower())

        # Check for conflicting signals
        positive_signals = ["safe", "acceptable", "within limit", "subcritical", "compliant"]
        negative_signals = ["unsafe", "unacceptable", "exceeds", "supercritical", "non-compliant"]

        has_positive = any(
            any(sig in c for sig in positive_signals)
            for c in conclusions
        )
        has_negative = any(
            any(sig in c for sig in negative_signals)
            for c in conclusions
        )

        # Conflict if one says safe and another says unsafe
        return has_positive and has_negative

    def _determine_priority(
        self, triggers: list[EscalationTrigger]
    ) -> EscalationPriority:
        """Determine escalation priority based on triggers."""
        if not triggers:
            return EscalationPriority.LOW

        # Safety basis and regulatory always bump to at least HIGH
        if any(t in triggers for t in [
            EscalationTrigger.SAFETY_BASIS_CHANGE,
            EscalationTrigger.REGULATORY_IMPLICATIONS,
        ]):
            if len(triggers) >= 2:
                return EscalationPriority.CRITICAL
            return EscalationPriority.HIGH

        # Use threshold mapping
        num_triggers = len(triggers)
        for threshold, priority in sorted(
            self.config.priority_thresholds.items(), reverse=True
        ):
            if num_triggers >= threshold:
                return priority

        return EscalationPriority.MEDIUM


@dataclass
class EscalationEvaluation:
    """Result of evaluating a query for escalation."""

    should_escalate: bool
    triggers: list[EscalationTrigger] = field(default_factory=list)
    priority: EscalationPriority = EscalationPriority.MEDIUM

    def __bool__(self) -> bool:
        """Allow using result in boolean context."""
        return self.should_escalate


def format_escalation_notice(request: EscalationRequest) -> str:
    """
    Format an escalation notice for the user.

    Args:
        request: EscalationRequest

    Returns:
        Formatted notice string
    """
    lines = [
        "Your query has been flagged for expert review.",
        "",
        f"**Request ID:** {request.request_id}",
        f"**Priority:** {request.priority.value.upper()}",
        "",
        "**Reason(s) for review:**",
    ]

    trigger_descriptions = {
        EscalationTrigger.NOVEL_CONFIGURATION: "Query involves a novel or untested configuration",
        EscalationTrigger.SAFETY_BASIS_CHANGE: "Query may affect existing safety basis documentation",
        EscalationTrigger.REGULATORY_IMPLICATIONS: "Response may have regulatory implications",
        EscalationTrigger.LOW_CONFIDENCE_HIGH_STAKES: "High-stakes query with lower confidence results",
        EscalationTrigger.CONFLICTING_REASONING: "Internal consistency check identified potential conflicts",
    }

    for trigger in request.triggers:
        desc = trigger_descriptions.get(trigger, trigger.value)
        lines.append(f"- {desc}")

    lines.extend([
        "",
        "A qualified expert will review your query and provide guidance.",
        "You will be notified when the review is complete.",
        "",
        "---",
        "*This escalation ensures critical safety questions receive appropriate expert oversight.*"
    ])

    return "\n".join(lines)
