"""
Stage 1: Normalization

Transform raw scraped reviews into clean, versioned records ready for LLM classification.

Responsibilities:
- Read raw reviews from input
- Text normalization (lowercase, whitespace, emoji)
- Language detection
- Content hash generation for deduplication
- Write to reviews_raw + reviews_enriched stub
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from reviewiq_pipeline.contracts import (
    NormalizedReview,
    RawReview,
    Stage1Input,
    Stage1Output,
    Stage1Stats,
)
from reviewiq_pipeline.services.text_processor import TextProcessor

if TYPE_CHECKING:
    from reviewiq_pipeline.config import Config
    from reviewiq_pipeline.db.connection import DatabasePool
    from reviewiq_pipeline.db.repositories import ReviewRepository

logger = logging.getLogger(__name__)


class Stage1Normalizer:
    """
    Stage 1: Normalize raw reviews for classification.

    This stage:
    1. Reads raw reviews from Stage 0 output
    2. Normalizes text (lowercase, whitespace, unicode)
    3. Detects language
    4. Generates content hash for deduplication
    5. Writes to reviews_raw and reviews_enriched tables
    """

    def __init__(
        self,
        config: Config,
        db: DatabasePool | None = None,
        review_repo: ReviewRepository | None = None,
    ):
        self.config = config
        self.db = db
        self.review_repo = review_repo
        self.text_processor = TextProcessor()

    async def process(self, input_data: Stage1Input) -> Stage1Output:
        """
        Process raw reviews through normalization stage.

        Args:
            input_data: Stage 1 input containing raw reviews

        Returns:
            Stage1Output with normalized reviews and stats
        """
        logger.info(
            f"Stage 1: Processing {len(input_data['reviews'])} reviews "
            f"for job {input_data['job_id']}"
        )

        normalized_reviews: list[NormalizedReview] = []
        stats = Stage1Stats(
            input_count=len(input_data["reviews"]),
            output_count=0,
            skipped_empty=0,
            skipped_duplicate=0,
        )

        seen_hashes: set[str] = set()

        for raw_review in input_data["reviews"]:
            try:
                result = self._normalize_review(
                    raw_review,
                    input_data["business_id"],
                    input_data["place_id"],
                )

                if result is None:
                    stats["skipped_empty"] += 1
                    continue

                # Check for duplicates within this batch
                if result["content_hash"] in seen_hashes:
                    stats["skipped_duplicate"] += 1
                    continue

                seen_hashes.add(result["content_hash"])

                # If we have a database, persist and check cross-batch duplicates
                if self.review_repo:
                    raw_id = await self._persist_review(raw_review, result, input_data)
                    result["raw_id"] = raw_id

                normalized_reviews.append(result)
                stats["output_count"] += 1

            except Exception as e:
                logger.error(f"Error normalizing review {raw_review.get('review_id')}: {e}")
                raise

        logger.info(
            f"Stage 1 complete: {stats['output_count']} normalized, "
            f"{stats['skipped_empty']} empty, {stats['skipped_duplicate']} duplicate"
        )

        return Stage1Output(
            job_id=input_data["job_id"],
            business_id=input_data["business_id"],
            place_id=input_data["place_id"],
            reviews_normalized=normalized_reviews,
            stats=stats,
        )

    def _normalize_review(
        self,
        raw: RawReview,
        business_id: str,
        place_id: str,
    ) -> NormalizedReview | None:
        """
        Normalize a single raw review.

        Args:
            raw: Raw review from scraper
            business_id: Business identifier
            place_id: Google Place ID

        Returns:
            NormalizedReview or None if should be skipped
        """
        text = raw.get("text")

        # Skip empty reviews (rating-only)
        if self.text_processor.is_empty_or_trivial(text):
            logger.debug(f"Skipping empty review {raw['review_id']}")
            return None

        # Normalize text
        norm_result = self.text_processor.normalize(text)  # type: ignore

        # Skip if normalized to empty
        if not norm_result.normalized:
            return None

        # Generate content hash
        content_hash = self.text_processor.generate_content_hash(norm_result.normalized)

        return NormalizedReview(
            source="google",
            review_id=raw["review_id"],
            review_version=1,
            business_id=business_id,
            place_id=place_id,
            text=text,  # type: ignore
            text_normalized=norm_result.normalized,
            text_language=norm_result.language,
            text_length=norm_result.char_count,
            word_count=norm_result.word_count,
            rating=raw["rating"],
            review_time=raw["review_time"],
            author_name=raw["author_name"],
            author_id=raw.get("author_id"),
            content_hash=content_hash,
            dedup_group_id=None,
        )

    async def _persist_review(
        self,
        raw: RawReview,
        normalized: NormalizedReview,
        input_data: Stage1Input,
    ) -> int:
        """
        Persist a normalized review to the database.

        Args:
            raw: Original raw review
            normalized: Normalized review data
            input_data: Stage 1 input for context

        Returns:
            The raw_id from reviews_raw table
        """
        if not self.review_repo:
            raise RuntimeError("ReviewRepository not configured")

        # Insert raw review
        raw_id = await self.review_repo.insert_raw_review(
            raw,
            input_data["place_id"],
            source="google",
        )

        # Insert enriched review stub with job_id
        await self.review_repo.insert_enriched_review(
            normalized,
            raw_id,
            job_id=input_data.get("job_id"),
        )

        return raw_id

    def normalize_batch(
        self,
        reviews: list[RawReview],
        business_id: str,
        place_id: str,
    ) -> list[NormalizedReview]:
        """
        Normalize a batch of reviews without database persistence.

        Useful for testing or when processing reviews in memory.

        Args:
            reviews: List of raw reviews
            business_id: Business identifier
            place_id: Google Place ID

        Returns:
            List of normalized reviews (skipped reviews excluded)
        """
        results = []
        seen_hashes: set[str] = set()

        for raw in reviews:
            normalized = self._normalize_review(raw, business_id, place_id)
            if normalized is None:
                continue
            if normalized["content_hash"] in seen_hashes:
                continue
            seen_hashes.add(normalized["content_hash"])
            results.append(normalized)

        return results
