"""
Extraction Quality Validation

Validates the quality of document extraction and provides
quality scores for content reliability assessment.
"""

import logging
import re
from dataclasses import dataclass, field
from enum import Enum

logger = logging.getLogger(__name__)


class QualityLevel(Enum):
    """Quality level classification."""

    EXCELLENT = "excellent"  # >95% confidence
    GOOD = "good"  # 85-95% confidence
    ACCEPTABLE = "acceptable"  # 70-85% confidence
    POOR = "poor"  # 50-70% confidence
    UNUSABLE = "unusable"  # <50% confidence


@dataclass
class QualityIssue:
    """Represents a quality issue found during validation."""

    issue_type: str
    severity: str  # "high", "medium", "low"
    description: str
    location: str | None = None  # Section/page where issue was found
    suggestion: str | None = None


@dataclass
class QualityScore:
    """
    Quality score for document extraction.

    Provides an overall quality assessment along with
    detailed issue tracking.
    """

    overall_score: float  # 0.0 to 1.0
    level: QualityLevel = QualityLevel.ACCEPTABLE

    # Component scores
    text_quality: float = 1.0
    structure_quality: float = 1.0
    table_quality: float = 1.0
    completeness: float = 1.0

    # Issues found
    issues: list[QualityIssue] = field(default_factory=list)

    # Statistics
    total_chars: int = 0
    total_words: int = 0
    garbage_char_ratio: float = 0.0
    avg_word_length: float = 0.0

    def __post_init__(self):
        """Set quality level based on overall score."""
        if self.overall_score >= 0.95:
            self.level = QualityLevel.EXCELLENT
        elif self.overall_score >= 0.85:
            self.level = QualityLevel.GOOD
        elif self.overall_score >= 0.70:
            self.level = QualityLevel.ACCEPTABLE
        elif self.overall_score >= 0.50:
            self.level = QualityLevel.POOR
        else:
            self.level = QualityLevel.UNUSABLE

    @property
    def is_acceptable(self) -> bool:
        """Check if quality is acceptable for use."""
        return self.level in [
            QualityLevel.EXCELLENT,
            QualityLevel.GOOD,
            QualityLevel.ACCEPTABLE,
        ]

    @property
    def high_severity_issues(self) -> list[QualityIssue]:
        """Get high severity issues."""
        return [i for i in self.issues if i.severity == "high"]

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "overall_score": self.overall_score,
            "level": self.level.value,
            "text_quality": self.text_quality,
            "structure_quality": self.structure_quality,
            "table_quality": self.table_quality,
            "completeness": self.completeness,
            "issues": [
                {
                    "type": i.issue_type,
                    "severity": i.severity,
                    "description": i.description,
                    "location": i.location,
                    "suggestion": i.suggestion,
                }
                for i in self.issues
            ],
            "stats": {
                "total_chars": self.total_chars,
                "total_words": self.total_words,
                "garbage_char_ratio": self.garbage_char_ratio,
                "avg_word_length": self.avg_word_length,
            },
        }


class QualityValidator:
    """
    Validates extraction quality for documents.

    Checks for:
    - Text quality (garbage characters, OCR artifacts)
    - Structural integrity (sections, tables preserved)
    - Completeness (no missing pages, truncation)
    - Technical content preservation (equations, formulas)

    Example:
        validator = QualityValidator()
        score = validator.validate(extracted_text)
        if not score.is_acceptable:
            print(f"Quality issues: {score.issues}")
    """

    # Characters that indicate OCR/extraction problems
    GARBAGE_CHARS = set("�□■▪▫◦○●◉◎★☆♦♣♠♥")

    # Patterns for common OCR artifacts
    OCR_ARTIFACT_PATTERNS = [
        r"[Il1]{4,}",  # Consecutive I/l/1 confusion
        r"[0O]{4,}",  # Consecutive 0/O confusion
        r"\b[a-z][A-Z]{2,}[a-z]\b",  # Mixed case in word
        r"[^\w\s]{5,}",  # Long sequences of special chars
    ]

    # Expected technical terms for NCS documents
    NCS_TECHNICAL_TERMS = [
        "criticality", "fissile", "fissionable", "neutron",
        "keff", "k-effective", "subcritical", "supercritical",
        "moderator", "reflector", "uranium", "plutonium",
        "enrichment", "mass limit", "concentration",
    ]

    def __init__(
        self,
        expected_pages: int | None = None,
        require_technical_terms: bool = True,
    ):
        """
        Initialize quality validator.

        Args:
            expected_pages: Expected number of pages (for completeness check)
            require_technical_terms: Whether to check for NCS terms
        """
        self.expected_pages = expected_pages
        self.require_technical_terms = require_technical_terms
        self._compiled_artifacts = [
            re.compile(p) for p in self.OCR_ARTIFACT_PATTERNS
        ]

    def validate(
        self,
        content: str,
        tables: list | None = None,
        sections: list | None = None,
        ocr_confidence: float | None = None,
    ) -> QualityScore:
        """
        Validate extraction quality.

        Args:
            content: Extracted text content
            tables: List of extracted tables (optional)
            sections: List of extracted sections (optional)
            ocr_confidence: OCR confidence score if applicable (optional)

        Returns:
            QualityScore with assessment
        """
        issues = []
        tables = tables or []
        sections = sections or []

        # Calculate basic statistics
        total_chars = len(content)
        words = content.split()
        total_words = len(words)
        avg_word_length = (
            sum(len(w) for w in words) / total_words if total_words > 0 else 0
        )

        # Check text quality
        text_score, text_issues = self._check_text_quality(content, total_chars)
        issues.extend(text_issues)

        # Check structure quality
        structure_score, structure_issues = self._check_structure_quality(
            content, sections
        )
        issues.extend(structure_issues)

        # Check table quality
        table_score, table_issues = self._check_table_quality(tables)
        issues.extend(table_issues)

        # Check completeness
        completeness_score, completeness_issues = self._check_completeness(
            content, total_words
        )
        issues.extend(completeness_issues)

        # Include OCR confidence if provided
        if ocr_confidence is not None:
            if ocr_confidence < 0.70:
                issues.append(QualityIssue(
                    issue_type="low_ocr_confidence",
                    severity="high",
                    description=f"OCR confidence is low ({ocr_confidence:.1%})",
                    suggestion="Consider manual review or re-scanning",
                ))
            text_score = min(text_score, ocr_confidence)

        # Calculate garbage char ratio
        garbage_chars = sum(1 for c in content if c in self.GARBAGE_CHARS)
        garbage_ratio = garbage_chars / total_chars if total_chars > 0 else 0

        # Calculate overall score
        overall_score = (
            text_score * 0.4 +
            structure_score * 0.2 +
            table_score * 0.2 +
            completeness_score * 0.2
        )

        return QualityScore(
            overall_score=overall_score,
            text_quality=text_score,
            structure_quality=structure_score,
            table_quality=table_score,
            completeness=completeness_score,
            issues=issues,
            total_chars=total_chars,
            total_words=total_words,
            garbage_char_ratio=garbage_ratio,
            avg_word_length=avg_word_length,
        )

    def _check_text_quality(
        self, content: str, total_chars: int
    ) -> tuple[float, list[QualityIssue]]:
        """Check text quality."""
        issues = []
        score = 1.0

        # Check for garbage characters
        garbage_count = sum(1 for c in content if c in self.GARBAGE_CHARS)
        if garbage_count > 0:
            ratio = garbage_count / total_chars if total_chars > 0 else 0
            if ratio > 0.01:
                issues.append(QualityIssue(
                    issue_type="garbage_characters",
                    severity="high" if ratio > 0.05 else "medium",
                    description=f"Found {garbage_count} garbage characters ({ratio:.1%})",
                    suggestion="Document may need re-extraction or OCR improvement",
                ))
                score -= min(ratio * 10, 0.3)

        # Check for OCR artifacts
        for pattern in self._compiled_artifacts:
            matches = pattern.findall(content)
            if matches:
                issues.append(QualityIssue(
                    issue_type="ocr_artifacts",
                    severity="medium",
                    description=f"Found potential OCR artifacts: {matches[:3]}",
                    suggestion="Review OCR quality, consider manual correction",
                ))
                score -= 0.05

        # Check for excessive whitespace
        whitespace_ratio = content.count("  ") / len(content) if content else 0
        if whitespace_ratio > 0.05:
            issues.append(QualityIssue(
                issue_type="excessive_whitespace",
                severity="low",
                description="Excessive whitespace in extracted text",
                suggestion="May indicate layout extraction issues",
            ))
            score -= 0.05

        # Check for technical terms (NCS documents should have them)
        if self.require_technical_terms:
            content_lower = content.lower()
            found_terms = sum(
                1 for term in self.NCS_TECHNICAL_TERMS
                if term in content_lower
            )
            if found_terms < 3:
                issues.append(QualityIssue(
                    issue_type="missing_technical_terms",
                    severity="medium",
                    description="Few NCS technical terms found",
                    suggestion="Verify this is an NCS document",
                ))
                score -= 0.1

        return max(score, 0.0), issues

    def _check_structure_quality(
        self, content: str, sections: list
    ) -> tuple[float, list[QualityIssue]]:
        """Check document structure quality."""
        issues = []
        score = 1.0

        # Check for section headers
        if not sections:
            # Try to detect sections in content
            section_patterns = [
                r"^#{1,6}\s+",
                r"^\d+\.\s+[A-Z]",
                r"^CHAPTER\s+\d+",
                r"^SECTION\s+\d+",
            ]
            found_sections = 0
            for pattern in section_patterns:
                found_sections += len(re.findall(pattern, content, re.MULTILINE))

            if found_sections < 3:
                issues.append(QualityIssue(
                    issue_type="missing_structure",
                    severity="medium",
                    description="Document structure not well preserved",
                    suggestion="Section headings may not have been extracted properly",
                ))
                score -= 0.2

        # Check for page markers
        page_markers = re.findall(r"---\s*Page\s+\d+\s*---", content)
        if page_markers:
            # Verify pages are sequential
            page_nums = []
            for m in page_markers:
                match = re.search(r"\d+", m)
                if match:
                    page_nums.append(int(match.group()))
            for i in range(1, len(page_nums)):
                if page_nums[i] != page_nums[i-1] + 1:
                    issues.append(QualityIssue(
                        issue_type="missing_pages",
                        severity="high",
                        description=f"Gap in page sequence: {page_nums[i-1]} to {page_nums[i]}",
                        suggestion="Some pages may not have been extracted",
                    ))
                    score -= 0.1

        return max(score, 0.0), issues

    def _check_table_quality(
        self, tables: list
    ) -> tuple[float, list[QualityIssue]]:
        """Check table extraction quality."""
        issues: list[QualityIssue] = []
        score = 1.0

        if not tables:
            return score, issues

        for i, table in enumerate(tables):
            # Check for empty tables
            headers = getattr(table, "headers", [])
            rows = getattr(table, "rows", [])

            if not headers and not rows:
                issues.append(QualityIssue(
                    issue_type="empty_table",
                    severity="medium",
                    description=f"Table {i+1} is empty",
                    location=f"Table {i+1}",
                ))
                score -= 0.05

            # Check for misaligned columns
            if headers and rows:
                header_cols = len(headers)
                for j, row in enumerate(rows):
                    if len(row) != header_cols:
                        issues.append(QualityIssue(
                            issue_type="misaligned_table",
                            severity="medium",
                            description=f"Table {i+1}, row {j+1} has {len(row)} cols, expected {header_cols}",
                            location=f"Table {i+1}",
                        ))
                        score -= 0.02

            # Check for empty cells
            total_cells = len(headers) + sum(len(row) for row in rows)
            empty_cells = sum(
                1 for row in [headers] + rows
                for cell in row if not str(cell).strip()
            )
            if total_cells > 0:
                empty_ratio = empty_cells / total_cells
                if empty_ratio > 0.3:
                    issues.append(QualityIssue(
                        issue_type="many_empty_cells",
                        severity="low",
                        description=f"Table {i+1} has {empty_ratio:.0%} empty cells",
                        location=f"Table {i+1}",
                    ))
                    score -= 0.05

        return max(score, 0.0), issues

    def _check_completeness(
        self, content: str, total_words: int
    ) -> tuple[float, list[QualityIssue]]:
        """Check document completeness."""
        issues = []
        score = 1.0

        # Check minimum content
        if total_words < 100:
            issues.append(QualityIssue(
                issue_type="insufficient_content",
                severity="high",
                description=f"Only {total_words} words extracted",
                suggestion="Document may not have been fully extracted",
            ))
            score -= 0.3

        # Check for truncation indicators
        truncation_patterns = [
            r"\[continued\]",
            r"\[truncated\]",
            r"\.{3,}$",  # Ends with ellipsis
            r"\[requires OCR\]",
        ]
        for pattern in truncation_patterns:
            if re.search(pattern, content, re.IGNORECASE):
                issues.append(QualityIssue(
                    issue_type="possible_truncation",
                    severity="medium",
                    description="Content may be truncated",
                    suggestion="Verify complete document extraction",
                ))
                score -= 0.1
                break

        # Check against expected pages
        if self.expected_pages:
            page_markers = re.findall(r"---\s*Page\s+\d+\s*---", content)
            extracted_pages = len(page_markers)
            if extracted_pages < self.expected_pages * 0.9:
                issues.append(QualityIssue(
                    issue_type="missing_pages",
                    severity="high",
                    description=f"Only {extracted_pages}/{self.expected_pages} pages extracted",
                    suggestion="Some pages were not extracted",
                ))
                score -= 0.2

        return max(score, 0.0), issues


def validate_extraction(
    content: str,
    **kwargs,
) -> QualityScore:
    """
    Convenience function to validate extraction quality.

    Args:
        content: Extracted text content
        **kwargs: Arguments passed to QualityValidator.validate()

    Returns:
        QualityScore with assessment
    """
    validator = QualityValidator()
    return validator.validate(content, **kwargs)
