from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from enum import Enum, auto


# Extraction Layer -----------------------------------------

@dataclass
class TextEntity:
    """
    Unified representation for extracted term
    """
    text: str                # normalized text content
    original_text: str       # original text content before normalization
    lemma: str               # lemma of term
    span: Tuple[int, int]    # start and end positions in the original text
    score: float             # candidate rating

    # context
    pos_tags: List[str] = field(default_factory=list)    # POS tags for the term tokens
    sentence: str = ""                                   # Sentence where the term occurs
    frequency: int = 1                                   # Frequency of the term in the document

    # metadata
    extractor_type: str = ""                                    # Which extractor produced this entity
    properties: Dict[str, Any] = field(default_factory=dict)    # Extra features (tf-idf, embedding, etc.)

    @property
    def normalized_form(self) -> str:
        return self.lemma.lower()

    def to_dict(self) -> Dict[str, Any]:
        """Serialize TextEntity to JSON-compatible dict"""
        return {
            "text": self.text,
            "original_text": self.original_text,
            "lemma": self.lemma,
            "span": list(self.span),
            "score": self.score,
            "pos_tags": self.pos_tags,
            "sentence": self.sentence,
            "frequency": self.frequency,
            "extractor_type": self.extractor_type,
            "properties": self.properties
        }


# Verification Layer -----------------------------------------

@dataclass(frozen=True)
class Entity:
    id:            str                                                   # unique identifier ("glossary:001", "ontology:1234", etc.)
    label:         str                                                   # "term", "concept", "entity", etc.
    synonyms:      List[str] = field(default_factory=list)               # list of synonymous names/labels for this entity
    relations:     Dict[str, List[str]] = field(default_factory=dict)    # relation type -> list of related entity ids
    definition:    Optional[str] = None                                  # textual definition or description of the entity
    source:        Optional[str] = None                                  # source of the entity (glossary name, ontology IRI, etc.)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize Entity to JSON-compatible dict"""
        result = {
            "id": self.id,
            "label": self.label,
            "synonyms": self.synonyms,
            "relations": self.relations
        }

        if self.definition is not None:
            result["definition"] = self.definition
        if self.source is not None:
            result["source"] = self.source

        return result


class MatchStatus(Enum):

    MATCHED = auto()
    UNKNOWN = auto()
    AMBIGUOUS = auto()
    NEAR_MATCH = auto()


@dataclass(frozen=True)
class MatchResult:
    text_entity: TextEntity
    entity: Optional[Entity] = None
    confidence: float = 0.0
    status: MatchStatus = MatchStatus.UNKNOWN
    matched_synonym: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize MatchResult to JSON-compatible dict"""
        return {
            "text_entity": self.text_entity.to_dict(),
            "entity": self.entity.to_dict() if self.entity else None,
            "confidence": self.confidence,
            "status": self.status.name,
            "matched_synonym": self.matched_synonym
        }


# Reporting Layer -----------------------------------------

class ReportType(str, Enum):
    """
    Types of reports that can be generated by the termlint pipeline
    """
    EXTRACTION = "extraction"              # extracted term candidates statistics
    PROCESSING = "processing"              # extracted and processed candidate statistics
    VERIFICATION = "verification"          # ontology coverage and matching statistics
    ONTOLOGY_UPDATE = "ontology_update"    # suggestions for ontology
    QUALITY_GATE = "quality_gate"          # CI/CD quality gate results (pass/fail)


@dataclass
class Report:
    """
    Standardized report format for termlint results, designed to be flexible for different report types.
    """
    report_type: ReportType

    # base metrics
    total_items: int
    processed_items: int

    # report type specific
    coverage_pct: Optional[float] = None
    unknown_terms: Optional[List[TextEntity]] = None
    suggested_entities: Optional[List[TextEntity]] = None
    matches: Optional[List[MatchResult]] = None
    quality_pass: Optional[bool] = None
    exit_code: Optional[int] = None

    # additional
    quality_score: Optional[float] = None
    raw_data: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert the report to a dictionary, excluding None values for cleaner output."""
        data: Dict[str, Any] = {
            "report_type": self.report_type,
            "total_items": self.total_items,
            "processed_items": self.processed_items
        }

        # type-specific fields
        if self.coverage_pct is not None:
            data["coverage_pct"] = self.coverage_pct
        if self.quality_score is not None:
            data["quality_score"] = self.quality_score
        if self.quality_pass is not None:
            data["quality_pass"] = self.quality_pass
        if self.exit_code is not None:
            data["exit_code"] = self.exit_code
        if self.unknown_terms is not None:
            data["unknown_terms"] = [t.to_dict() for t in self.unknown_terms]
        if self.suggested_entities is not None:
            data["suggested_entities"] = [t.to_dict() for t in self.suggested_entities]
        if self.matches is not None:
            data["matches"] = [m.to_dict() for m in self.matches]
        if self.raw_data:
            data["raw_data"] = self.raw_data

        return data

    @property
    def is_success(self) -> bool:
        """Convenience property to check if the quality gate passed based on the quality_pass field."""
        return self.quality_pass is True


@dataclass
class ReportConfig:
    """
    Configuration for generating reports in the termlint pipeline,
    allowing users to specify which reports to include and how to export them.
    """
    include: List[ReportType] = field(default_factory=lambda: [ReportType.VERIFICATION])
    exporters: List[str] = field(default_factory=lambda: ["json"])
    output_dir: Path = field(default_factory=lambda: Path.cwd() / "reports/")
    quality_gates: Optional[Dict[str, Any]] = None


@dataclass
class QualityConfig:
    """
    """
    min_coverage: float = 90.
    max_unknown: int = 5
    min_quality_score: float = 0.8

    def check(self, reports: List[Report]) -> bool:
        """
        Check if the provided reports meet the quality gate criteria.
        """
        verification = next((r for r in reports if r.report_type == ReportType.VERIFICATION), None)
        if not verification or verification.coverage_pct is None:
            return False

        if verification.coverage_pct < self.min_coverage:
            return False

        unknown_count = len(verification.unknown_terms or [])
        return unknown_count <= self.max_unknown
