"""
Stage 3: Issue Routing

Route classified spans to issues (create new or aggregate to existing).

Responsibilities:
- Query unrouted V-/V± spans
- Generate deterministic issue IDs
- Create/update issues with span counts
- Insert issue_spans links
- Log events for audit trail
"""

from __future__ import annotations

import hashlib
import logging
from typing import TYPE_CHECKING

from reviewiq_pipeline.contracts import (
    RoutedSpan,
    SpanToRoute,
    Stage3Input,
    Stage3Output,
    Stage3Stats,
)

if TYPE_CHECKING:
    from reviewiq_pipeline.config import Config
    from reviewiq_pipeline.db.connection import DatabasePool
    from reviewiq_pipeline.db.repositories import IssueRepository, SpanRepository

logger = logging.getLogger(__name__)


class Stage3Router:
    """
    Stage 3: Route negative/mixed spans to issues.

    This stage:
    1. Queries unrouted spans with V- or V± valence
    2. Generates deterministic issue IDs from routing keys
    3. Creates new issues or updates existing ones
    4. Links spans to issues (1:1 mapping)
    5. Logs events for audit trail
    """

    def __init__(
        self,
        config: Config,
        db: DatabasePool | None = None,
        span_repo: SpanRepository | None = None,
        issue_repo: IssueRepository | None = None,
    ):
        self.config = config
        self.db = db
        self.span_repo = span_repo
        self.issue_repo = issue_repo

    async def process(self, input_data: Stage3Input) -> Stage3Output:
        """
        Process spans through routing stage.

        Args:
            input_data: Stage 3 input with spans to route

        Returns:
            Stage3Output with routing results and stats
        """
        logger.info(f"Stage 3: Routing {len(input_data['spans'])} spans")

        # Get job_id from input (may be None)
        job_id = input_data.get("job_id")

        routed_spans: list[RoutedSpan] = []
        issues_created: list[str] = []
        issues_updated: list[str] = []
        spans_skipped = 0

        for span in input_data["spans"]:
            try:
                # Skip positive spans
                if span["valence"] not in ("V-", "V±"):
                    spans_skipped += 1
                    continue

                routed = await self._route_span(span, job_id=job_id)
                if routed:
                    routed_spans.append(routed)

                    if routed["is_new_issue"]:
                        issues_created.append(routed["issue_id"])
                    else:
                        if routed["issue_id"] not in issues_updated:
                            issues_updated.append(routed["issue_id"])

            except Exception as e:
                logger.error(f"Error routing span {span['span_id']}: {e}")
                raise

        logger.info(
            f"Stage 3 complete: {len(routed_spans)} routed, "
            f"{len(issues_created)} issues created, "
            f"{len(issues_updated)} issues updated"
        )

        return Stage3Output(
            routed_spans=routed_spans,
            issues_created=issues_created,
            issues_updated=issues_updated,
            stats=Stage3Stats(
                spans_processed=len(input_data["spans"]),
                spans_routed=len(routed_spans),
                spans_skipped=spans_skipped,
                issues_created=len(issues_created),
                issues_updated=len(issues_updated),
            ),
        )

    async def _route_span(self, span: SpanToRoute, job_id: str | None = None) -> RoutedSpan | None:
        """
        Route a single span to an issue.

        Args:
            span: Span to route
            job_id: Optional job ID to link issues to pipeline executions

        Returns:
            RoutedSpan with routing info, or None if skipped
        """
        # Generate routing key and issue ID
        routing_key = self._generate_routing_key(span)
        issue_id = self._generate_issue_id(routing_key)

        # Check if span already routed (should not happen, but defensive)
        if self.issue_repo:
            existing_issue = await self.issue_repo.check_span_already_linked(span["span_id"])
            if existing_issue:
                logger.warning(
                    f"Span {span['span_id']} already linked to {existing_issue}"
                )
                return None

        # Create or update issue
        is_new_issue = True
        if self.issue_repo:
            is_new_issue = await self.issue_repo.upsert_issue(
                issue_id=issue_id,
                business_id=span["business_id"],
                place_id=span["place_id"],
                primary_subcode=span["urt_primary"],
                intensity=span["intensity"],
                entity=span.get("entity_normalized"),
                entity_normalized=span.get("entity_normalized"),
                taxonomy_version=self.config.taxonomy_version,
                job_id=job_id,
            )

        routed = RoutedSpan(
            span_id=span["span_id"],
            issue_id=issue_id,
            routing_key=routing_key,
            is_new_issue=is_new_issue,
        )

        # Link span to issue
        if self.issue_repo:
            await self.issue_repo.link_span_to_issue(
                routed=routed,
                source="google",  # Assuming Google source
                review_id="",  # Would need to be passed from span metadata
                review_version=1,
                intensity=span["intensity"],
                review_time=span["review_time"],
                is_primary_match=True,
            )

            # Log event
            event_type = "issue_created" if is_new_issue else "span_added"
            await self.issue_repo.log_event(
                issue_id=issue_id,
                event_type=event_type,
                span_id=span["span_id"],
                metadata={
                    "urt_primary": span["urt_primary"],
                    "valence": span["valence"],
                    "intensity": span["intensity"],
                },
            )

        return routed

    def _generate_routing_key(self, span: SpanToRoute) -> str:
        """
        Generate routing key for a span.

        Format: business_id|place_id|urt_primary|entity_normalized

        Args:
            span: Span to generate key for

        Returns:
            Routing key string
        """
        entity = span.get("entity_normalized") or ""
        return f"{span['business_id']}|{span['place_id']}|{span['urt_primary']}|{entity}"

    def _generate_issue_id(self, routing_key: str) -> str:
        """
        Generate deterministic issue ID from routing key.

        Args:
            routing_key: Routing key string

        Returns:
            Issue ID in format ISS-{hash16}
        """
        hash_value = hashlib.sha256(routing_key.encode()).hexdigest()
        return f"ISS-{hash_value[:16]}"

    async def process_from_db(self, limit: int = 100) -> Stage3Output:
        """
        Process unrouted spans directly from database.

        Convenience method that queries unrouted spans and processes them.

        Args:
            limit: Maximum number of spans to process

        Returns:
            Stage3Output with routing results
        """
        if not self.span_repo:
            raise RuntimeError("SpanRepository not configured")

        # Query unrouted negative spans
        span_rows = await self.span_repo.get_unrouted_negative_spans(limit)

        # Convert to SpanToRoute format
        spans = [
            SpanToRoute(
                span_id=row["span_id"],
                business_id=row["business_id"],
                place_id=row["place_id"],
                urt_primary=row["urt_primary"],
                valence=row["valence"],
                intensity=row["intensity"],
                entity_normalized=row.get("entity_normalized"),
                review_time=str(row["review_time"]),
                confidence=row["confidence"],
                trust_score=row.get("trust_score", 0.5),
            )
            for row in span_rows
        ]

        return await self.process(Stage3Input(spans=spans))

    def route_span_sync(self, span: SpanToRoute) -> RoutedSpan:
        """
        Route a span without database operations (for testing).

        Args:
            span: Span to route

        Returns:
            RoutedSpan with routing info
        """
        if span["valence"] not in ("V-", "V±"):
            raise ValueError(f"Cannot route positive span (valence={span['valence']})")

        routing_key = self._generate_routing_key(span)
        issue_id = self._generate_issue_id(routing_key)

        return RoutedSpan(
            span_id=span["span_id"],
            issue_id=issue_id,
            routing_key=routing_key,
            is_new_issue=True,  # Can't know without DB
        )
