"""Engagement anomaly detection — detect and alert on engagement irregularities.

Anomaly types:
1. Duplicate post engagement: Same post_id engaged multiple times by same account
2. Excessive same-company engagement: Multiple comments on same company's posts in 24h
3. Comment clustering: Burst of engagements in short time window
4. Failed engagement spike: Unusual number of failed engagements

Anomalies are stored as scheduler events and optionally trigger email alerts
via the backend API.
"""

from __future__ import annotations

import json
import logging
import time
from dataclasses import asdict, dataclass, field
from typing import Any

from ..constants import (
    ANOMALY_BURST_THRESHOLD,
    ANOMALY_BURST_WINDOW_MINUTES,
    ANOMALY_DUPLICATE_POST_THRESHOLD,
    ANOMALY_FAILED_SPIKE_THRESHOLD,
    ANOMALY_SAME_COMPANY_THRESHOLD,
)
from ..db.queries import (
    get_duplicate_post_engagements,
    get_engagement_burst_count,
    get_failed_engagement_count,
    get_same_company_engagements,
    log_scheduler_event,
)

logger = logging.getLogger(__name__)


@dataclass
class Anomaly:
    """Detected engagement anomaly."""

    anomaly_type: str  # 'duplicate_post', 'same_company_cluster', 'burst', 'failed_spike'
    severity: str  # 'warning', 'critical'
    summary: str  # Human-readable description
    details: dict = field(default_factory=dict)
    detected_at: int = 0

    def __post_init__(self) -> None:
        if not self.detected_at:
            self.detected_at = int(time.time())


def detect_anomalies(hours: int = 24) -> list[Anomaly]:
    """Run all anomaly detection checks. Returns list of detected anomalies."""
    anomalies: list[Anomaly] = []

    # 1. Duplicate post engagement (same post, same account)
    duplicates = get_duplicate_post_engagements(hours=hours)
    for dup in duplicates:
        if dup["cnt"] > ANOMALY_DUPLICATE_POST_THRESHOLD:
            anomalies.append(Anomaly(
                anomaly_type="duplicate_post",
                severity="critical" if dup["cnt"] >= 3 else "warning",
                summary=(
                    f"Post {dup['post_id'][:30]}... was engaged {dup['cnt']} times "
                    f"by account {(dup.get('account_id') or '?')[:10]}..."
                ),
                details={
                    "post_id": dup["post_id"],
                    "account_id": dup.get("account_id", ""),
                    "count": dup["cnt"],
                    "outreach_ids": dup["outreach_ids"],
                    "action_types": dup["action_types"],
                    "first_at": dup["first_at"],
                    "last_at": dup["last_at"],
                },
            ))

    # 2. Same-company clustering
    clusters = get_same_company_engagements(hours=hours)
    for cluster in clusters:
        if cluster["engagement_count"] > ANOMALY_SAME_COMPANY_THRESHOLD:
            anomalies.append(Anomaly(
                anomaly_type="same_company_cluster",
                severity="warning",
                summary=(
                    f"{cluster['engagement_count']} comments on posts related to "
                    f"'{cluster['company']}' in {hours}h"
                ),
                details={
                    "company": cluster["company"],
                    "prospect_name": cluster["prospect_name"],
                    "post_count": cluster["post_count"],
                    "engagement_count": cluster["engagement_count"],
                    "post_ids": cluster["post_ids"],
                },
            ))

    # 3. Burst detection
    burst_count = get_engagement_burst_count(minutes=ANOMALY_BURST_WINDOW_MINUTES)
    if burst_count > ANOMALY_BURST_THRESHOLD:
        anomalies.append(Anomaly(
            anomaly_type="burst",
            severity="warning" if burst_count < ANOMALY_BURST_THRESHOLD * 2 else "critical",
            summary=(
                f"{burst_count} engagements in the last {ANOMALY_BURST_WINDOW_MINUTES} min "
                f"(threshold: {ANOMALY_BURST_THRESHOLD})"
            ),
            details={
                "count": burst_count,
                "window_minutes": ANOMALY_BURST_WINDOW_MINUTES,
                "threshold": ANOMALY_BURST_THRESHOLD,
            },
        ))

    # 4. Failed engagement spike
    failed_count = get_failed_engagement_count(hours=hours)
    if failed_count > ANOMALY_FAILED_SPIKE_THRESHOLD:
        anomalies.append(Anomaly(
            anomaly_type="failed_spike",
            severity="warning" if failed_count < ANOMALY_FAILED_SPIKE_THRESHOLD * 2 else "critical",
            summary=(
                f"{failed_count} failed engagements in the last {hours}h "
                f"(threshold: {ANOMALY_FAILED_SPIKE_THRESHOLD})"
            ),
            details={
                "failed_count": failed_count,
                "hours": hours,
                "threshold": ANOMALY_FAILED_SPIKE_THRESHOLD,
            },
        ))

    return anomalies


def log_anomalies(anomalies: list[Anomaly]) -> None:
    """Log detected anomalies as scheduler events."""
    for anomaly in anomalies:
        log_scheduler_event(
            f"anomaly_{anomaly.anomaly_type}",
            context={
                "severity": anomaly.severity,
                "summary": anomaly.summary,
                **anomaly.details,
            },
        )
        logger.warning(
            "Engagement anomaly [%s/%s]: %s",
            anomaly.anomaly_type,
            anomaly.severity,
            anomaly.summary,
        )


async def run_anomaly_scan() -> list[Anomaly]:
    """Run a full anomaly scan, log results, and trigger alerts.

    Called by the scheduler every ANOMALY_SCAN_SECONDS.
    """
    anomalies = detect_anomalies(hours=24)

    if anomalies:
        log_anomalies(anomalies)

        # Send email alert for critical anomalies via backend
        critical = [a for a in anomalies if a.severity == "critical"]
        if critical:
            await _send_anomaly_alert(critical)

    return anomalies


async def _send_anomaly_alert(anomalies: list[Anomaly]) -> None:
    """Send alert via backend API endpoint."""
    try:
        from ..config import get_backend_config

        backend_url, jwt_token = get_backend_config()
        if not backend_url or not jwt_token:
            logger.debug("No backend configured, skipping anomaly alert email")
            return

        import httpx

        payload = {"anomalies": [asdict(a) for a in anomalies]}

        async with httpx.AsyncClient(timeout=15.0) as http:
            resp = await http.post(
                f"{backend_url}/api/v1/alerts/engagement-anomaly",
                json=payload,
                headers={
                    "Authorization": f"Bearer {jwt_token}",
                    "Content-Type": "application/json",
                },
            )
            if resp.status_code == 200:
                logger.info("Anomaly alert sent to backend (%d anomalies)", len(anomalies))
            else:
                logger.warning(
                    "Anomaly alert failed: HTTP %d — %s",
                    resp.status_code,
                    resp.text[:100],
                )
    except Exception as e:
        logger.warning("Failed to send anomaly alert: %s", e)


def check_post_engagement_anomaly(post_id: str, account_id: str = "") -> Anomaly | None:
    """Real-time check: call after each engagement to detect immediate duplicates.

    Returns an Anomaly if this post_id was already engaged more than once, or None.
    This is a lightweight single-post check, not a full scan.
    """
    if not post_id:
        return None

    from ..db.schema import get_db

    db = get_db()
    if account_id:
        row = db.execute(
            "SELECT COUNT(*) as c FROM engagements WHERE post_id = ? AND account_id = ?",
            (post_id, account_id),
        ).fetchone()
    else:
        row = db.execute(
            "SELECT COUNT(*) as c FROM engagements WHERE post_id = ?",
            (post_id,),
        ).fetchone()
    db.close()

    count = row["c"] if row else 0
    if count > 1:  # Already engaged more than once
        return Anomaly(
            anomaly_type="duplicate_post",
            severity="critical" if count >= 3 else "warning",
            summary=f"Post {post_id[:30]}... now has {count} engagements from same account",
            details={"post_id": post_id, "account_id": account_id, "total_engagements": count},
        )
    return None
