"""Execute a single parity test — compare DW query result vs report value."""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from .contracts import (
    ParityTestResult,
    ReportLineItem,
    ReportSpec,
    ValidationPeriod,
    ValidationSlice,
    _new_id,
)
from .query_generator import QueryGenerator

logger = logging.getLogger(__name__)


class ParityTestExecutor:
    """Execute parity tests comparing DW query results against expected report values."""

    def __init__(
        self,
        tolerance_pct: float = 0.01,
        schema_prefix: str = "",
        query_executor: Any = None,
    ):
        self._tolerance_pct = tolerance_pct
        self._query_gen = QueryGenerator(schema_prefix=schema_prefix)
        self._query_executor = query_executor

    def execute_test(
        self,
        spec: ReportSpec,
        line_item: ReportLineItem,
        period: ValidationPeriod,
        period_value: str,
        actual_value: Optional[float] = None,
    ) -> ParityTestResult:
        """Execute a single parity test.

        Args:
            spec: The report spec
            line_item: The line item to test
            period: Temporal period level
            period_value: Specific period (e.g. "2024-01")
            actual_value: Pre-computed actual value (if None, query is generated but not executed)
        """
        expected = line_item.expected_values.get(period_value, 0.0)
        dw_query = self._query_gen.generate_query(spec, line_item, period, period_value)

        if actual_value is None:
            if self._query_executor:
                try:
                    actual_value = self._query_executor(dw_query)
                except Exception as e:
                    logger.error("Query execution failed: %s", e)
                    return ParityTestResult(
                        slice=ValidationSlice(period=period, period_value=period_value),
                        line_item_id=line_item.line_id,
                        expected_value=expected,
                        actual_value=0.0,
                        difference=expected,
                        difference_pct=1.0,
                        status="error",
                        dw_query=dw_query,
                        timestamp=datetime.now(timezone.utc).isoformat(),
                    )
            else:
                actual_value = 0.0

        diff = abs(expected - actual_value)
        diff_pct = diff / abs(expected) if expected != 0 else (0.0 if diff == 0 else 1.0)

        if diff_pct <= self._tolerance_pct:
            status = "pass"
        elif diff_pct <= self._tolerance_pct * 5:
            status = "warn"
        else:
            status = "fail"

        return ParityTestResult(
            slice=ValidationSlice(period=period, period_value=period_value),
            line_item_id=line_item.line_id,
            expected_value=expected,
            actual_value=actual_value,
            difference=round(diff, 6),
            difference_pct=round(diff_pct, 6),
            status=status,
            tolerance_pct=self._tolerance_pct,
            dw_query=dw_query,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )

    def execute_slice(
        self,
        spec: ReportSpec,
        validation_slice: ValidationSlice,
        actual_values: Optional[Dict[str, float]] = None,
    ) -> List[ParityTestResult]:
        """Execute parity tests for an entire validation slice.

        Args:
            spec: The report spec
            validation_slice: Which scope + period to test
            actual_values: {line_item_id: actual_value} — pre-computed values
        """
        results: List[ParityTestResult] = []
        items = self._resolve_items(spec, validation_slice)
        actuals = actual_values or {}

        for item in items:
            periods = self._resolve_periods(item, validation_slice)
            for period_val in periods:
                actual = actuals.get(f"{item.line_id}:{period_val}", actuals.get(item.line_id))
                result = self.execute_test(
                    spec, item, validation_slice.period, period_val, actual,
                )
                results.append(result)

        return results

    def _resolve_items(
        self,
        spec: ReportSpec,
        validation_slice: ValidationSlice,
    ) -> List[ReportLineItem]:
        """Resolve line items based on scope."""
        from .contracts import ValidationScope

        if validation_slice.line_item_ids:
            id_set = set(validation_slice.line_item_ids)
            return [li for li in spec.line_items if li.line_id in id_set]

        if validation_slice.scope == ValidationScope.LINE_ITEM:
            return spec.line_items[:1] if spec.line_items else []
        elif validation_slice.scope == ValidationScope.LINE_GROUP:
            if spec.groups:
                group = spec.groups[0]
                return [li for li in spec.line_items if li.group == group]
        return spec.line_items

    @staticmethod
    def _resolve_periods(
        item: ReportLineItem,
        validation_slice: ValidationSlice,
    ) -> List[str]:
        """Resolve which periods to test."""
        if validation_slice.period_value:
            return [validation_slice.period_value]
        if item.expected_values:
            return sorted(item.expected_values.keys())[:1]
        return [""]
