"""Tests for Stage 1: Normalization."""

from __future__ import annotations

import pytest

from reviewiq_pipeline.services.text_processor import (
    TextProcessor,
    is_valid_iso639,
    is_valid_sha256,
)
from reviewiq_pipeline.stages.stage1_normalize import Stage1Normalizer
from reviewiq_pipeline.validation.validators import validate_stage1_output


class TestTextProcessor:
    """Tests for the TextProcessor service."""

    def test_normalize_basic(self):
        """Test basic text normalization."""
        processor = TextProcessor()
        result = processor.normalize("  Hello   World!  ")

        assert result.normalized == "hello world!"
        assert result.word_count == 2
        assert result.char_count == 12

    def test_normalize_unicode(self):
        """Test Unicode normalization."""
        processor = TextProcessor()
        # NFC normalization test
        result = processor.normalize("café")

        assert "cafe" in result.normalized or "café" in result.normalized

    def test_normalize_control_chars(self):
        """Test removal of control characters."""
        processor = TextProcessor()
        result = processor.normalize("Hello\x00World\x1fTest")

        assert "\x00" not in result.normalized
        assert "\x1f" not in result.normalized

    def test_detect_language_english(self):
        """Test English language detection."""
        processor = TextProcessor()
        lang = processor.detect_language("This is a test sentence in English.")

        assert lang == "en"

    def test_generate_content_hash(self):
        """Test content hash generation."""
        processor = TextProcessor()
        hash1 = processor.generate_content_hash("test content")
        hash2 = processor.generate_content_hash("test content")
        hash3 = processor.generate_content_hash("different content")

        assert hash1 == hash2  # Same input = same hash
        assert hash1 != hash3  # Different input = different hash
        assert len(hash1) == 64  # SHA256 hex length

    def test_is_empty_or_trivial(self):
        """Test empty/trivial text detection."""
        processor = TextProcessor()

        assert processor.is_empty_or_trivial(None) is True
        assert processor.is_empty_or_trivial("") is True
        assert processor.is_empty_or_trivial("  ") is True
        assert processor.is_empty_or_trivial("ab") is True
        assert processor.is_empty_or_trivial("abc") is False
        assert processor.is_empty_or_trivial("Hello world") is False


class TestHelperFunctions:
    """Tests for helper functions."""

    def test_is_valid_iso639(self):
        """Test ISO 639-1 validation."""
        assert is_valid_iso639("en") is True
        assert is_valid_iso639("es") is True
        assert is_valid_iso639("fr") is True
        assert is_valid_iso639("de") is True
        assert is_valid_iso639("xx") is False
        assert is_valid_iso639("") is False
        assert is_valid_iso639("english") is False

    def test_is_valid_sha256(self):
        """Test SHA256 hash validation."""
        valid_hash = "a" * 64
        invalid_short = "a" * 63
        invalid_long = "a" * 65
        invalid_chars = "g" * 64  # 'g' is not hex

        assert is_valid_sha256(valid_hash) is True
        assert is_valid_sha256(invalid_short) is False
        assert is_valid_sha256(invalid_long) is False
        assert is_valid_sha256(invalid_chars) is False
        assert is_valid_sha256("") is False
        assert is_valid_sha256(None) is False  # type: ignore


class TestStage1Normalizer:
    """Tests for Stage 1 normalizer."""

    def test_normalize_review_basic(self, sample_raw_review):
        """Test basic review normalization."""
        from reviewiq_pipeline.config import Config

        config = Config()
        normalizer = Stage1Normalizer(config)

        result = normalizer._normalize_review(
            sample_raw_review,
            "test-business",
            "test-place",
        )

        assert result is not None
        assert result["source"] == "google"
        assert result["review_id"] == sample_raw_review["review_id"]
        assert result["text"] == sample_raw_review["text"]
        assert result["text_normalized"] is not None
        assert result["text_language"] == "en"
        assert len(result["content_hash"]) == 64

    def test_normalize_empty_review(self):
        """Test that empty reviews are skipped."""
        from reviewiq_pipeline.config import Config

        config = Config()
        normalizer = Stage1Normalizer(config)

        empty_review = {
            "review_id": "test-empty",
            "author_name": "Test",
            "rating": 5,
            "text": "",
            "review_time": "2026-01-20T14:30:00Z",
        }

        result = normalizer._normalize_review(empty_review, "test-business", "test-place")
        assert result is None

    def test_normalize_batch(self, sample_raw_review):
        """Test batch normalization."""
        from reviewiq_pipeline.config import Config

        config = Config()
        normalizer = Stage1Normalizer(config)

        reviews = [
            sample_raw_review,
            {
                "review_id": "second-review",
                "author_name": "Jane",
                "rating": 5,
                "text": "Great service!",
                "review_time": "2026-01-21T10:00:00Z",
            },
        ]

        results = normalizer.normalize_batch(reviews, "test-business", "test-place")
        assert len(results) == 2

    def test_normalize_deduplication(self, sample_raw_review):
        """Test that duplicate reviews are detected."""
        from reviewiq_pipeline.config import Config

        config = Config()
        normalizer = Stage1Normalizer(config)

        # Two reviews with same text
        reviews = [
            sample_raw_review,
            {**sample_raw_review, "review_id": "duplicate-review"},
        ]

        results = normalizer.normalize_batch(reviews, "test-business", "test-place")
        assert len(results) == 1  # Duplicate should be filtered


class TestStage1Validation:
    """Tests for Stage 1 validation."""

    def test_validate_valid_output(self, sample_stage1_output):
        """Test validation of valid Stage 1 output."""
        result = validate_stage1_output(sample_stage1_output)

        assert result["stage"] == "stage1"
        assert result["passed"] is True
        assert result["error_count"] == 0

    def test_validate_empty_text(self, sample_stage1_output):
        """Test validation catches empty text."""
        sample_stage1_output["reviews_normalized"][0]["text"] = ""

        result = validate_stage1_output(sample_stage1_output)

        assert result["passed"] is False
        assert any(e["rule"] == "V1.1" for e in result["errors"])

    def test_validate_invalid_hash(self, sample_stage1_output):
        """Test validation catches invalid content hash."""
        sample_stage1_output["reviews_normalized"][0]["content_hash"] = "invalid"

        result = validate_stage1_output(sample_stage1_output)

        assert result["passed"] is False
        assert any(e["rule"] == "V1.3" for e in result["errors"])

    def test_validate_invalid_language(self, sample_stage1_output):
        """Test validation catches invalid language code."""
        sample_stage1_output["reviews_normalized"][0]["text_language"] = "invalid"

        result = validate_stage1_output(sample_stage1_output)

        assert result["passed"] is False
        assert any(e["rule"] == "V1.5" for e in result["errors"])
