"""Evidence sampling engine — pull small data samples to validate extracted logic."""
from __future__ import annotations

import hashlib
import re
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional

from .constants import EVIDENCE_SAMPLE_MAX_ROWS, EVIDENCE_SAMPLE_MONTHS
from .contracts import EvidenceSample, LogicArtifact, Measure


class EvidenceSampler:
    """Build and execute evidence-sampling queries for logic validation.

    Generates SQL queries that pull small data samples from the source tables
    referenced by a LogicArtifact.  Samples are bounded by date range
    (``EVIDENCE_SAMPLE_MONTHS``) and row limit (``EVIDENCE_SAMPLE_MAX_ROWS``).
    """

    def __init__(
        self,
        max_rows: int = EVIDENCE_SAMPLE_MAX_ROWS,
        sample_months: int = EVIDENCE_SAMPLE_MONTHS,
    ):
        self.max_rows = max_rows
        self.sample_months = sample_months

    # ------------------------------------------------------------------
    # Query building
    # ------------------------------------------------------------------

    def build_sample_query(
        self,
        artifact: LogicArtifact,
        *,
        date_column: str = "",
        extra_filters: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Build a SQL query to collect an evidence sample.

        Returns a dict with ``sql``, ``table``, ``columns``, and ``filter_desc``.
        """
        # Identify primary table from dependencies
        table = self._pick_primary_table(artifact)
        if not table:
            return {"error": "No source table found in artifact dependencies."}

        # Identify columns to select
        columns = self._pick_columns(artifact)
        col_clause = ", ".join(columns) if columns else "*"

        # Build WHERE clause
        where_parts: List[str] = []
        filter_desc_parts: List[str] = []

        # Date filter
        if date_column:
            cutoff = (
                datetime.now(timezone.utc) - timedelta(days=30 * self.sample_months)
            ).strftime("%Y-%m-%d")
            where_parts.append(f"{date_column} >= '{cutoff}'")
            filter_desc_parts.append(f"{date_column} >= '{cutoff}'")

        # Carry over artifact filters
        for fp in artifact.objects.filters:
            if fp.source_clause:
                where_parts.append(fp.source_clause)
                filter_desc_parts.append(fp.source_clause)
            elif fp.column and fp.value:
                clause = f"{fp.column} {fp.operator.value} '{fp.value}'"
                where_parts.append(clause)
                filter_desc_parts.append(clause)

        # Extra caller-supplied filters
        if extra_filters:
            where_parts.extend(extra_filters)
            filter_desc_parts.extend(extra_filters)

        where_clause = (" WHERE " + " AND ".join(where_parts)) if where_parts else ""
        limit_clause = f" LIMIT {self.max_rows}"

        sql = f"SELECT {col_clause} FROM {table}{where_clause}{limit_clause}"

        return {
            "sql": sql,
            "table": table,
            "columns": columns or ["*"],
            "filter_desc": ", ".join(filter_desc_parts) if filter_desc_parts else "none",
        }

    def build_measure_verify_query(
        self,
        measure: Measure,
        table: str,
        *,
        date_column: str = "",
        grain_columns: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Build a SQL query to verify a specific measure against source data.

        Returns a dict with ``sql``, ``table``, ``measure_name``, and ``expression``.
        """
        if not table:
            return {"error": "Table name is required."}

        expression = measure.expression or f"{measure.aggregation.value}({measure.name})"
        grain_cols = grain_columns or []

        select_parts = list(grain_cols) + [f"{expression} AS measure_value"]
        select_clause = ", ".join(select_parts)

        where_parts: List[str] = []
        if date_column:
            cutoff = (
                datetime.now(timezone.utc) - timedelta(days=30 * self.sample_months)
            ).strftime("%Y-%m-%d")
            where_parts.append(f"{date_column} >= '{cutoff}'")

        where_clause = (" WHERE " + " AND ".join(where_parts)) if where_parts else ""
        group_clause = (" GROUP BY " + ", ".join(grain_cols)) if grain_cols else ""
        limit_clause = f" LIMIT {self.max_rows}"

        sql = f"SELECT {select_clause} FROM {table}{where_clause}{group_clause}{limit_clause}"

        return {
            "sql": sql,
            "table": table,
            "measure_name": measure.name or measure.alias,
            "expression": expression,
        }

    # ------------------------------------------------------------------
    # Sample collection (stubs — actual execution needs a connection)
    # ------------------------------------------------------------------

    def collect_sample(
        self,
        artifact: LogicArtifact,
        *,
        date_column: str = "",
        extra_filters: Optional[List[str]] = None,
        rows: Optional[List[Dict[str, Any]]] = None,
    ) -> EvidenceSample:
        """Collect an evidence sample for the given artifact.

        If ``rows`` are provided (e.g., from an external connection), they are
        used directly.  Otherwise the method returns an EvidenceSample with
        ``row_count=0`` to indicate that execution is pending.
        """
        query_info = self.build_sample_query(
            artifact, date_column=date_column, extra_filters=extra_filters,
        )

        if "error" in query_info:
            return EvidenceSample(
                artifact_id=artifact.artifact_id,
                filter_desc=query_info["error"],
            )

        row_count = len(rows) if rows else 0
        sample_hash = self._hash_rows(rows) if rows else ""

        return EvidenceSample(
            artifact_id=artifact.artifact_id,
            table_name=query_info["table"],
            filter_desc=query_info["filter_desc"],
            row_count=row_count,
            columns=query_info["columns"],
            sample_hash=sample_hash,
            masked=False,
        )

    def verify_measure(
        self,
        measure: Measure,
        artifact: LogicArtifact,
        *,
        date_column: str = "",
        result_rows: Optional[List[Dict[str, Any]]] = None,
    ) -> Dict[str, Any]:
        """Verify a measure against source data.

        Returns verification status with the query used and sample statistics.
        """
        table = self._pick_primary_table(artifact)
        if not table:
            return {"status": "error", "reason": "No source table found."}

        grain_cols = [gc.column for gc in artifact.objects.grain_columns]

        query_info = self.build_measure_verify_query(
            measure, table, date_column=date_column, grain_columns=grain_cols,
        )

        if "error" in query_info:
            return {"status": "error", "reason": query_info["error"]}

        if result_rows:
            values = [r.get("measure_value") for r in result_rows if "measure_value" in r]
            numeric_vals = [v for v in values if isinstance(v, (int, float))]

            return {
                "status": "verified",
                "measure_name": query_info["measure_name"],
                "expression": query_info["expression"],
                "sql": query_info["sql"],
                "row_count": len(result_rows),
                "sample_stats": {
                    "min": min(numeric_vals) if numeric_vals else None,
                    "max": max(numeric_vals) if numeric_vals else None,
                    "count": len(numeric_vals),
                },
            }

        return {
            "status": "pending",
            "measure_name": query_info["measure_name"],
            "expression": query_info["expression"],
            "sql": query_info["sql"],
            "message": "Execute the SQL against the source to verify.",
        }

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    def _pick_primary_table(self, artifact: LogicArtifact) -> str:
        """Pick the primary table from artifact dependencies."""
        deps = artifact.objects.dependencies
        if not deps:
            return ""

        # Prefer actual tables over views/CTEs
        tables = [d for d in deps if d.dep_type == "table"]
        if tables:
            dep = tables[0]
        else:
            dep = deps[0]

        parts = []
        if dep.database:
            parts.append(dep.database)
        if dep.schema_name:
            parts.append(dep.schema_name)
        parts.append(dep.name)
        return ".".join(parts)

    def _pick_columns(self, artifact: LogicArtifact) -> List[str]:
        """Collect relevant columns from the artifact."""
        cols: List[str] = []

        # Grain columns
        for gc in artifact.objects.grain_columns:
            if gc.column and gc.column not in cols:
                cols.append(gc.column)

        # Measure source columns
        for m in artifact.objects.measures:
            for sc in m.source_columns:
                if sc and sc not in cols:
                    cols.append(sc)

        # Filter columns
        for f in artifact.objects.filters:
            col = f.column
            # Strip table prefix
            if "." in col:
                col = col.split(".")[-1]
            if col and col not in cols:
                cols.append(col)

        return cols

    @staticmethod
    def _hash_rows(rows: List[Dict[str, Any]]) -> str:
        """Generate a SHA-256 hash of sample rows for integrity."""
        content = str(sorted(str(r) for r in rows))
        return hashlib.sha256(content.encode()).hexdigest()[:16]

