"""
Data models for quality assurance.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any

from pydantic import BaseModel, Field, field_validator


class QualityScore(BaseModel):
    """Quality score metrics for a dataset."""

    overall_score: float = Field(ge=0.0, le=100.0)  # 0-100
    violation_rate: float = Field(
        ge=0.0, le=1.0
    )  # 0-1 (percentage of records with violations)
    completeness: float = Field(
        ge=0.0, le=1.0
    )  # 0-1 (percentage of required fields present)
    accuracy: float = Field(ge=0.0, le=1.0)  # 0-1 (percentage of valid records)
    field_scores: dict[str, float | None] = Field(
        default_factory=dict
    )  # Per-field scores
    record_count: int = 0
    valid_count: int = 0
    invalid_count: int = 0


class FieldQualityMetrics(BaseModel):
    """Quality metrics for a specific field."""

    field_name: str
    null_count: int = 0
    violation_count: int = 0
    total_count: int = 0
    violation_rate: float = 0.0
    completeness: float = 1.0
    error_types: dict[str, int] | None = Field(
        default_factory=dict
    )  # Error type -> count

    def calculate_metrics(self):
        """Calculate derived metrics."""
        if self.total_count > 0:
            self.violation_rate = self.violation_count / self.total_count
            self.completeness = 1.0 - (self.null_count / self.total_count)
        else:
            self.violation_rate = 0.0
            self.completeness = 0.0


class QualityThresholds(BaseModel):
    """Quality thresholds for alerting."""

    min_overall_score: float = 95.0
    max_violation_rate: float = 0.05  # 5%
    min_completeness: float = 0.95  # 95%
    min_accuracy: float = 0.95  # 95%
    field_thresholds: dict[str, dict[str, float]] = (
        {}
    )  # field_name -> {metric: threshold}

    def check(self, quality_score: QualityScore) -> list[str]:
        """
        Check if quality score breaches any thresholds.

        Returns:
            List of threshold breach messages (empty if all pass)
        """
        breaches = []

        if quality_score.overall_score < self.min_overall_score:
            breaches.append(
                f"Overall score {quality_score.overall_score:.2f} "
                f"below threshold {self.min_overall_score:.2f}"
            )

        if quality_score.violation_rate > self.max_violation_rate:
            breaches.append(
                f"Violation rate {quality_score.violation_rate:.2%} "
                f"exceeds threshold {self.max_violation_rate:.2%}"
            )

        if quality_score.completeness < self.min_completeness:
            breaches.append(
                f"Completeness {quality_score.completeness:.2%} "
                f"below threshold {self.min_completeness:.2%}"
            )

        if quality_score.accuracy < self.min_accuracy:
            breaches.append(
                f"Accuracy {quality_score.accuracy:.2%} "
                f"below threshold {self.min_accuracy:.2%}"
            )

        # Check field-specific thresholds
        for field_name, thresholds in self.field_thresholds.items():
            if field_name in quality_score.field_scores:
                field_score = quality_score.field_scores[field_name]
                if "min_score" in thresholds and field_score < thresholds["min_score"]:
                    breaches.append(
                        f"Field '{field_name}' score {field_score:.2f} "
                        f"below threshold {thresholds['min_score']:.2f}"
                    )

        return breaches


class QualityCheckOptions(BaseModel):
    """Options for quality checks."""

    record_violations: bool = True
    calculate_metrics: bool = True
    check_thresholds: bool = False
    thresholds: QualityThresholds | None = None
    include_field_metrics: bool = True
    include_profiling: bool = False  # Include data profiling in report
    sample_size: int | None = None  # If set, only check a sample of records
    metadata: dict[str, Any] | None = Field(default_factory=dict)  # Additional metadata

    # Data version tracking and deduplication
    data_version: str | None = None  # Version identifier for the dataset
    data_source: str | None = None  # Source identifier (file path, table name, etc.)
    skip_if_unchanged: bool = (
        False  # Skip check if data hasn't changed (requires data_fingerprint)
    )
    deduplicate_violations: bool = (
        True  # Deduplicate violations for same record+field+error
    )

    @classmethod
    def basic(cls) -> QualityCheckOptions:
        """Create options for quick one-off quality checks.

        Enables metrics and violation recording. Disables profiling and
        threshold checking for speed.

        Returns:
            QualityCheckOptions configured for basic checks.
        """
        return cls(
            record_violations=True,
            calculate_metrics=True,
            check_thresholds=False,
            thresholds=None,
            include_field_metrics=True,
            include_profiling=False,
        )

    @classmethod
    def strict(cls) -> QualityCheckOptions:
        """Create options for gated quality checks.

        Enables all features including profiling and threshold checking
        with default thresholds. Use this when quality must meet minimum
        standards before proceeding.

        Returns:
            QualityCheckOptions configured for strict checks.
        """
        return cls(
            record_violations=True,
            calculate_metrics=True,
            check_thresholds=True,
            thresholds=QualityThresholds(),
            include_field_metrics=True,
            include_profiling=True,
        )

    @classmethod
    def monitoring(cls) -> QualityCheckOptions:
        """Create options for scheduled/recurring quality checks.

        Enables all features plus deduplication and skip-if-unchanged
        to avoid redundant work in monitoring pipelines.

        Returns:
            QualityCheckOptions configured for monitoring.
        """
        return cls(
            record_violations=True,
            calculate_metrics=True,
            check_thresholds=True,
            thresholds=QualityThresholds(),
            include_field_metrics=True,
            include_profiling=True,
            skip_if_unchanged=True,
            deduplicate_violations=True,
        )


class QualityReport(BaseModel):
    """Complete quality check report."""

    schema_id: str
    schema_version: str | None = None
    check_timestamp: datetime = Field(default_factory=datetime.utcnow)
    quality_score: QualityScore | None = None
    field_metrics: dict[str, FieldQualityMetrics] = Field(default_factory=dict)
    violation_count: int = 0
    record_count: int = 0
    valid_count: int = 0
    invalid_count: int = 0
    threshold_breaches: list[str] = Field(default_factory=list)
    passed: bool = True
    metadata: dict[str, Any] = Field(default_factory=dict)

    def model_post_init(self, __context):
        """Calculate passed status after initialization."""
        # Calculate passed status based on threshold breaches
        if self.threshold_breaches:
            self.passed = False
        elif self.quality_score:
            # If no thresholds set, pass if accuracy > 0
            self.passed = self.quality_score.accuracy > 0
