"""
Common Room signal ingestion utilities.

Provides a minimal CommonRoomClient for webhook verification, payload
normalization, and CSV-driven batch imports used by the g-gremlin CLI.
"""

from __future__ import annotations

import csv
import hashlib
import hmac
import time
import warnings
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Protocol, Tuple


PERSONAL_EMAIL_DOMAINS = {"gmail.com", "yahoo.com", "hotmail.com", "outlook.com"}
DEFAULT_TIME_WINDOW_SECONDS = 300  # 5 minutes replay protection


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def _parse_timestamp(value: str) -> datetime:
    """Parse ISO8601 timestamp strings with UTC fallback."""
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(timezone.utc)
    except Exception:
        return _utc_now()


def _hash_email(value: str) -> str:
    return hashlib.sha256(value.strip().lower().encode("utf-8")).hexdigest()


def _hash_path(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()


@dataclass
class CommonRoomSignal:
    """Normalized intent signal from Common Room."""

    # Identity
    signal_id: str
    correlation_id: str

    # Signal metadata
    signal_type: str
    intent_score: float
    timestamp: datetime

    # Company
    domain: str
    company_name: Optional[str]

    # Person
    person_email: Optional[str]
    person_name: Optional[str]
    person_title: Optional[str]
    person_linkedin_url: Optional[str]

    # Resolution results (post-enrichment)
    resolved_account_id: Optional[str] = None
    resolved_account_name: Optional[str] = None
    resolution_provider: Optional[str] = None
    resolution_confidence: Optional[float] = None
    activation_eligible: bool = False
    eligibility_reason: Optional[str] = None

    # Raw payload
    raw_payload: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        result = asdict(self)
        result["timestamp"] = self.timestamp.isoformat()
        return result


class SignalSourceProvider(Protocol):
    """Interface for intent signal sources."""

    @property
    def name(self) -> str:
        ...

    def verify_webhook(self, request_body: bytes, signature: str, timestamp: str) -> bool:
        ...

    def normalize_event(self, raw_event: Dict[str, Any]) -> CommonRoomSignal:
        ...

    def test_connection(self) -> Dict[str, Any]:
        ...

    def list_recent_signals(self, since: datetime, limit: int = 100) -> List[CommonRoomSignal]:
        ...


class CommonRoomClient(SignalSourceProvider):
    """Minimal Common Room client for webhook + CSV import flows."""

    def __init__(
        self,
        webhook_secret: Optional[str] = None,
        api_key: Optional[str] = None,
        profile: str = "default",
    ) -> None:
        self._webhook_secret = webhook_secret or ""
        self._api_key = api_key or ""
        self._profile = profile

    @property
    def name(self) -> str:
        return "commonroom"

    def _compute_signature(self, body: bytes, timestamp: str) -> str:
        message = f"{timestamp}.".encode("utf-8") + body
        digest = hmac.new(self._webhook_secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
        return f"sha256={digest}"

    def verify_webhook(self, request_body: bytes, signature: str, timestamp: str) -> bool:
        """Validate HMAC signature and replay window."""
        if not self._webhook_secret:
            return False

        try:
            sent_ts = int(timestamp)
        except (TypeError, ValueError):
            return False

        now = int(time.time())
        if abs(now - sent_ts) > DEFAULT_TIME_WINDOW_SECONDS:
            return False

        expected = self._compute_signature(request_body, timestamp)
        return hmac.compare_digest(expected, signature or "")

    def normalize_event(self, raw_event: Dict[str, Any]) -> CommonRoomSignal:
        """Map Common Room webhook payload into CommonRoomSignal."""
        signal_id = str(raw_event.get("event_id") or raw_event.get("id") or "")
        signal_type = raw_event.get("event_type") or raw_event.get("type") or "unknown"
        ts_raw = raw_event.get("timestamp") or raw_event.get("created_at") or _utc_now().isoformat()
        company = raw_event.get("company") or {}
        person = raw_event.get("person") or {}

        domain = (company.get("domain") or "").lower().strip()
        company_name = company.get("name")
        person_email = person.get("email")
        person_name = person.get("name")
        person_title = person.get("title")
        person_linkedin = person.get("linkedin_url") or person.get("linkedin") or None
        intent_score = float(raw_event.get("intent_score") or 0)

        correlation_id = build_correlation_id(signal_type, signal_id)

        return CommonRoomSignal(
            signal_id=signal_id or correlation_id,
            correlation_id=correlation_id,
            signal_type=signal_type,
            intent_score=intent_score,
            timestamp=_parse_timestamp(ts_raw),
            domain=domain,
            company_name=company_name,
            person_email=person_email,
            person_name=person_name,
            person_title=person_title,
            person_linkedin_url=person_linkedin,
            raw_payload=raw_event,
        )

    def test_connection(self) -> Dict[str, Any]:
        """Placeholder for API health checks (CSV/webhook-first rollout)."""
        status = "ok" if self._webhook_secret or self._api_key else "not_configured"
        return {
            "status": status,
            "profile": self._profile,
            "webhook_secret_configured": bool(self._webhook_secret),
            "api_key_configured": bool(self._api_key),
        }

    def list_recent_signals(self, since: datetime, limit: int = 100) -> List[CommonRoomSignal]:
        """
        Webhook-first architecture: pull API not supported in this client.

        Common Room production usage is webhook/CSV export driven; this stub is
        retained for backward compatibility but deliberately not implemented.
        """
        warnings.warn(
            "CommonRoom pull API is not implemented; use webhook or CSV export flows.",
            DeprecationWarning,
            stacklevel=2,
        )
        raise NotImplementedError("Common Room API pull not implemented; use CSV export.")


def build_correlation_id(signal_type: str, signal_id: str, *, batch_hash: str | None = None, row_index: int | None = None) -> str:
    """Generate correlation IDs for webhook or batch paths."""
    if signal_id:
        return f"cr:{signal_type}:{signal_id}"
    if batch_hash is not None and row_index is not None:
        return f"cr:batch:{batch_hash}:{row_index}"
    # Fallback to timestamp-based correlation
    return f"cr:{signal_type}:{int(time.time())}"


def evaluate_activation(signal: CommonRoomSignal, min_intent_score: float = 0.0) -> Tuple[bool, str]:
    """Simple eligibility heuristic for demo-grade loop closure."""
    if signal.intent_score < min_intent_score:
        return False, f"intent_score<{min_intent_score}"
    if not signal.domain:
        return False, "missing_domain"
    if signal.person_email and signal.person_email.split("@")[-1].lower() in PERSONAL_EMAIL_DOMAINS:
        return False, "personal_email"
    return True, "eligible"


def dedupe_signals(signals: Iterable[CommonRoomSignal]) -> List[CommonRoomSignal]:
    """Remove duplicates by (domain, signal_type, signal_id)."""
    seen = set()
    deduped: List[CommonRoomSignal] = []
    for signal in signals:
        key = (signal.domain, signal.signal_type, signal.signal_id)
        if key in seen:
            continue
        seen.add(key)
        deduped.append(signal)
    return deduped


def load_signals_from_csv(
    source: Path,
    *,
    since: Optional[datetime] = None,
    min_intent_score: float = 0.0,
    signal_types: Optional[List[str]] = None,
    resolve_companies: bool = False,
) -> Tuple[List[CommonRoomSignal], Dict[str, Any]]:
    """
    Load and normalize Common Room signals from a CSV export.

    Supported headers: signal_id, signal_type, domain, company_name,
    person_email, person_name, person_title, person_linkedin_url,
    intent_score, timestamp.
    """
    if not source.exists():
        raise FileNotFoundError(f"Source file not found: {source}")

    batch_hash = _hash_path(source)[:8]
    signals: List[CommonRoomSignal] = []

    with source.open("r", encoding="utf-8") as handle:
        reader = csv.DictReader(handle)
        for idx, row in enumerate(reader):
            signal_type = row.get("signal_type") or row.get("event_type") or "unknown"
            signal_id = row.get("signal_id") or row.get("event_id") or ""
            intent_score = float(row.get("intent_score") or row.get("score") or 0.0)
            ts_raw = row.get("timestamp") or row.get("created_at") or _utc_now().isoformat()

            if signal_types and signal_type not in signal_types:
                continue

            signal = CommonRoomSignal(
                signal_id=signal_id or f"row-{idx}",
                correlation_id=build_correlation_id(signal_type, signal_id, batch_hash=batch_hash, row_index=idx),
                signal_type=signal_type,
                intent_score=intent_score,
                timestamp=_parse_timestamp(ts_raw),
                domain=(row.get("domain") or "").strip().lower(),
                company_name=row.get("company_name") or row.get("company"),
                person_email=row.get("person_email") or row.get("email"),
                person_name=row.get("person_name") or row.get("name"),
                person_title=row.get("person_title") or row.get("title"),
                person_linkedin_url=row.get("person_linkedin_url") or row.get("linkedin_url"),
                raw_payload=row,
            )

            if since and signal.timestamp < since:
                continue

            eligible, reason = evaluate_activation(signal, min_intent_score=min_intent_score)
            signal.activation_eligible = eligible
            signal.eligibility_reason = reason

            if resolve_companies and signal.domain:
                signal.resolution_provider = "foundrygraph"
                signal.resolution_confidence = 0.5  # placeholder until real resolver is wired

            signals.append(signal)

    deduped = dedupe_signals(signals)

    stats = {
        "source": str(source),
        "rows_read": len(signals),
        "deduped": len(deduped),
        "eligible": len([s for s in deduped if s.activation_eligible]),
        "batch_hash": batch_hash,
    }
    return deduped, stats


__all__ = [
    "CommonRoomSignal",
    "SignalSourceProvider",
    "CommonRoomClient",
    "load_signals_from_csv",
    "evaluate_activation",
    "dedupe_signals",
    "build_correlation_id",
]
