"""Detect unsupported dependencies and runtime patterns."""

from __future__ import annotations

import asyncio
import sys
from typing import Any, Dict, List, Optional

# Map imports to unsupported codes
UNSUPPORTED_IMPORTS = {
    # Redis (aioredis only - sync redis is now supported)
    "aioredis": {
        "code": "UNSUPPORTED_ASYNC_REDIS",
        "message": "aioredis async Redis client not yet supported",
        "severity": "error",
    },
    # Task queues
    "celery": {
        "code": "UNSUPPORTED_TASK_QUEUE",
        "message": "Celery task queue not yet supported",
        "severity": "error",
    },
    "dramatiq": {
        "code": "UNSUPPORTED_TASK_QUEUE",
        "message": "Dramatiq task queue not yet supported",
        "severity": "error",
    },
    # Alternative async runtimes
    "trio": {
        "code": "UNSUPPORTED_TRIO_RUNTIME",
        "message": "Trio async runtime not yet supported",
        "severity": "error",
    },
    # MongoDB (motor only - sync pymongo is now supported)
    "motor": {
        "code": "UNSUPPORTED_ASYNC_MONGODB",
        "message": "motor async MongoDB driver not yet supported",
        "severity": "error",
    },
    # Elasticsearch
    "elasticsearch": {
        "code": "UNSUPPORTED_ELASTICSEARCH",
        "message": "elasticsearch-py not yet supported",
        "severity": "error",
    },
}

# Partial support (warnings, not errors)
PARTIAL_SUPPORT_IMPORTS = {
    "sqlalchemy": {
        "code": "PARTIAL_SQLALCHEMY",
        "message": "SQLAlchemy detected; capture at DBAPI layer only (psycopg/psycopg2)",
        "severity": "warning",
    },
    "sqlalchemy.ext.asyncio": {
        "code": "UNSUPPORTED_ASYNC_ORM",
        "message": "SQLAlchemy async sessions not yet supported",
        "severity": "error",
    },
    "anyio": {
        "code": "PARTIAL_ANYIO",
        "message": "anyio detected; async support is experimental",
        "severity": "warning",
    },
    "grpc": {
        "code": "PARTIAL_GRPC",
        "message": "gRPC detected; only unary-unary calls supported (streaming not yet supported)",
        "severity": "warning",
    },
    "boto3": {
        "code": "PARTIAL_BOTO3",
        "message": "boto3 detected; only S3 read operations supported (GetObject, HeadObject, ListObjectsV2)",
        "severity": "warning",
    },
    "botocore": {
        "code": "PARTIAL_BOTO3",
        "message": "botocore detected; only S3 read operations supported (GetObject, HeadObject, ListObjectsV2)",
        "severity": "warning",
    },
    "pymongo": {
        "code": "PARTIAL_PYMONGO",
        "message": "pymongo detected; only read operations supported (find, find_one, count_documents, aggregate, distinct)",
        "severity": "warning",
    },
    # Async drivers with full capture + replay support
    "asyncpg": {
        "code": "SUPPORTED_ASYNC_DB",
        "message": "asyncpg detected; capture and replay supported",
        "severity": "info",
    },
    "aiohttp": {
        "code": "SUPPORTED_ASYNC_HTTP",
        "message": "aiohttp detected; capture and replay supported",
        "severity": "info",
    },
    "aiomysql": {
        "code": "SUPPORTED_ASYNC_DB",
        "message": "aiomysql detected; capture and replay supported",
        "severity": "info",
    },
    "aiosqlite": {
        "code": "SUPPORTED_ASYNC_DB",
        "message": "aiosqlite detected; capture and replay supported",
        "severity": "info",
    },
}


def detect_unsupported_imports() -> List[Dict[str, Any]]:
    """Scan sys.modules for unsupported imports.

    Returns list of detected unsupported dependencies.
    """
    detected = []

    # Check unsupported imports
    for module_name, info in UNSUPPORTED_IMPORTS.items():
        if module_name in sys.modules:
            detected.append({
                "import": module_name,
                "code": info["code"],
                "message": info["message"],
                "severity": info["severity"],
            })

    # Check partial support imports
    for module_name, info in PARTIAL_SUPPORT_IMPORTS.items():
        if module_name in sys.modules:
            detected.append({
                "import": module_name,
                "code": info["code"],
                "message": info["message"],
                "severity": info["severity"],
            })

    return detected


def detect_async_runtime() -> Dict[str, Any]:
    """Detect if async runtime is active.

    Returns dict with async detection results.
    """
    result = {
        "async_detected": False,
        "runtime": None,
        "framework": None,
        "event_loop_running": False,
    }

    # Check for running event loop
    try:
        loop = asyncio.get_running_loop()
        result["async_detected"] = True
        result["runtime"] = "asyncio"
        result["event_loop_running"] = True
    except RuntimeError:
        # No running loop, but might still have async code
        pass

    # Check for pytest-asyncio
    if "pytest_asyncio" in sys.modules:
        result["async_detected"] = True
        result["framework"] = "pytest-asyncio"
        result["runtime"] = result["runtime"] or "asyncio"

    # Check for anyio
    if "anyio" in sys.modules:
        result["async_detected"] = True
        result["framework"] = result["framework"] or "anyio"
        result["runtime"] = result["runtime"] or "anyio"

    # Check for trio
    if "trio" in sys.modules:
        result["async_detected"] = True
        result["runtime"] = "trio"
        result["framework"] = "trio"

    return result


def detect_threading() -> Dict[str, Any]:
    """Detect if threading is used with I/O.

    Returns dict with threading detection results.
    """
    import threading

    result = {
        "threading_detected": False,
        "active_threads": 0,
        "warning": None,
    }

    # Count non-main threads
    active = threading.active_count()
    if active > 1:
        result["threading_detected"] = True
        result["active_threads"] = active
        result["warning"] = f"{active} threads active; multi-threaded I/O may cause non-determinism"

    return result


def get_full_detection() -> Dict[str, Any]:
    """Run all detection checks and return combined results.

    Returns dict with:
    - unsupported_detected: list of unsupported imports
    - async_info: async runtime detection
    - threading_info: threading detection
    - has_blocking_unsupported: bool if any severity=error unsupported found
    """
    unsupported = detect_unsupported_imports()
    async_info = detect_async_runtime()
    threading_info = detect_threading()

    # Check if any blocking (error severity) issues
    has_blocking = any(
        item["severity"] == "error"
        for item in unsupported
    )

    # A running asyncio loop is supported by probe async patches and should
    # not be treated as unsupported on its own.

    return {
        "unsupported_detected": unsupported,
        "async_info": async_info,
        "threading_info": threading_info,
        "has_blocking_unsupported": has_blocking,
    }


def classify_run_status(detection: Dict[str, Any], validation_result: Optional[Dict] = None) -> str:
    """Classify run status based on detection and validation.

    Returns one of:
    - REPRO_READY: Deterministic, no unsupported deps
    - FAILED_UNSUPPORTED: Uses unsupported dependency
    - FAILED_UNSTABLE: Non-deterministic across runs
    - FAILED_CAPTURE: Capture itself failed
    - FAILED_VALIDATION: Validation harness error
    """
    # Check for blocking unsupported dependencies
    if detection.get("has_blocking_unsupported"):
        return "FAILED_UNSUPPORTED"

    # If no validation result yet, can't determine further
    if validation_result is None:
        return "PENDING"

    # Check validation results
    if validation_result.get("error"):
        return "FAILED_VALIDATION"

    if validation_result.get("stable") and validation_result.get("determinism_score", 0) >= 1.0:
        return "REPRO_READY"

    if not validation_result.get("stable"):
        return "FAILED_UNSTABLE"

    # Default for partial determinism
    return "FAILED_UNSTABLE"


def get_primary_unsupported_reason(detection: Dict[str, Any]) -> Optional[Dict[str, str]]:
    """Get the primary unsupported reason for reporting.

    Returns dict with code and message, or None if no unsupported.
    """
    unsupported = detection.get("unsupported_detected", [])

    # Find first error-severity issue
    for item in unsupported:
        if item["severity"] == "error":
            return {
                "code": item["code"],
                "message": item["message"],
                "import": item["import"],
            }

    # A running asyncio event loop is supported by probe async patches —
    # do not treat it as unsupported.

    return None
