from __future__ import annotations

import uuid
from datetime import datetime, timedelta
from typing import Any, Mapping, Optional, Tuple

from receipts.base import BaseReceipt
from receipts.domains.governance import TimestampAnomaly, TimestampPolicyReceipt

# Allow limited future-facing predictions while flagging extreme values.
_DEFAULT_MAX_FUTURE_LEAD = timedelta(days=7)
# Flag late writes when the observation lags reality by more than a month.
_DEFAULT_MAX_PAST_LAG = timedelta(days=30)


def _policy_receipt_id(source: BaseReceipt, suffix: str) -> str:
    token = uuid.uuid4().hex[:8]
    return f"{source.receipt_id}-timestamp-{suffix}-{token}"


def _coerce_days(value: Any, default: timedelta) -> timedelta:
    if value is None:
        return default
    try:
        numeric = float(value)
    except (TypeError, ValueError):
        return default
    if numeric < 0:
        return default
    return timedelta(days=numeric)


def _load_gatebook_policy() -> Mapping[str, Any]:
    try:
        from ops.gatebook import load_gatebook  # inline import to dodge circulars
    except Exception:
        return {}
    try:
        payload = load_gatebook()
    except Exception:
        return {}
    config = payload.get("timestamp_policy")
    return config if isinstance(config, Mapping) else {}


def _resolve_policy_windows() -> Tuple[timedelta, timedelta]:
    config = _load_gatebook_policy()
    lead_window = _coerce_days(config.get("lead_days"), _DEFAULT_MAX_FUTURE_LEAD)
    lag_window = _coerce_days(config.get("lag_days"), _DEFAULT_MAX_PAST_LAG)
    return lead_window, lag_window


def evaluate_timestamp_policy(
    receipt: BaseReceipt,
    *,
    system_time: datetime,
) -> Optional[TimestampPolicyReceipt]:
    """
    Evaluate timestamp hygiene for a receipt.

    Returns:
        TimestampPolicyReceipt when the payload violates policy, or None when compliant.
    """

    if isinstance(receipt, TimestampPolicyReceipt):
        return None

    auto_valid_time = bool(getattr(receipt, "_auto_valid_time", False))
    valid_time = getattr(receipt, "valid_time", None)
    if auto_valid_time or valid_time is None:
        return TimestampPolicyReceipt(
            receipt_id=_policy_receipt_id(receipt, "missing-valid"),
            target_receipt_id=receipt.receipt_id,
            target_receipt_type=receipt.type,
            target_domain=receipt.domain,
            anomaly=TimestampAnomaly.MISSING_VALID_TIME,
            observed_valid_time=None,
            observed_system_time=system_time,
            policy_threshold_seconds=0.0,
            notes="valid_time missing or auto-filled at write; substituted with system_time",
            valid_time=system_time,
        )

    skew = system_time - valid_time
    skew_seconds = skew.total_seconds()
    max_future_lead, max_past_lag = _resolve_policy_windows()
    max_future_lead_seconds = max_future_lead.total_seconds()
    max_past_lag_seconds = max_past_lag.total_seconds()

    if skew_seconds > max_past_lag_seconds:
        return TimestampPolicyReceipt(
            receipt_id=_policy_receipt_id(receipt, "lag"),
            target_receipt_id=receipt.receipt_id,
            target_receipt_type=receipt.type,
            target_domain=receipt.domain,
            anomaly=TimestampAnomaly.EXCESSIVE_LAG,
            observed_valid_time=valid_time,
            observed_system_time=system_time,
            skew_seconds=skew_seconds,
            policy_threshold_seconds=max_past_lag_seconds,
            notes="system_time trails valid_time beyond allowed lag window",
            valid_time=system_time,
        )

    if skew_seconds < -max_future_lead_seconds:
        return TimestampPolicyReceipt(
            receipt_id=_policy_receipt_id(receipt, "future-lead"),
            target_receipt_id=receipt.receipt_id,
            target_receipt_type=receipt.type,
            target_domain=receipt.domain,
            anomaly=TimestampAnomaly.EXCESSIVE_LEAD,
            observed_valid_time=valid_time,
            observed_system_time=system_time,
            skew_seconds=skew_seconds,
            policy_threshold_seconds=max_future_lead_seconds,
            notes="valid_time leads system_time beyond allowed prediction window",
            valid_time=system_time,
        )

    return None
