"""SQLAlchemy detection and monitoring for AutoDebug.

This module provides:
1. Detection of SQLAlchemy version and usage patterns
2. Session boundary tracking
3. ORM pattern detection for stability analysis
4. AsyncSession detection for SQLAlchemy 2.0
"""

from __future__ import annotations

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

if TYPE_CHECKING:
    from autodebug_probe.recorder import Recorder

# Global recorder reference
_recorder: Optional["Recorder"] = None

# Patched functions
_original_session_execute: Optional[Any] = None
_original_async_session_init: Optional[Any] = None

# ORM patterns that may cause instability
UNSTABLE_ORM_PATTERNS = [
    "session.query(...).all()",  # Order may vary without ORDER BY
    "relationship lazy loading",  # Timing dependent
    "session.refresh()",  # May hit DB unexpectedly
    "deferred column loading",  # Order can vary
]


def _get_recorder() -> "Recorder":
    """Get the global recorder instance."""
    if _recorder is None:
        from autodebug_probe.recorder import get_recorder
        return get_recorder()
    return _recorder


def detect_sqlalchemy() -> Optional[Dict[str, Any]]:
    """Detect SQLAlchemy usage and configuration.

    Returns None if SQLAlchemy is not installed/imported.
    Otherwise returns dict with:
        - version: SQLAlchemy version string
        - major_version: Major version number (1 or 2)
        - orm_used: Whether ORM is imported
        - core_only: Whether only Core is used
        - async_orm: Whether async ORM is used
        - async_engine: Whether AsyncEngine is used
        - session_class: Name of session class in use
    """
    if "sqlalchemy" not in sys.modules:
        return None

    try:
        from sqlalchemy import __version__
    except ImportError:
        return None

    result: Dict[str, Any] = {
        "version": __version__,
        "major_version": int(__version__.split(".")[0]),
        "orm_used": "sqlalchemy.orm" in sys.modules,
        "core_only": "sqlalchemy.orm" not in sys.modules,
        "async_orm": False,
        "async_engine": False,
        "session_class": None,
        "connection_pool_type": None,
    }

    # Detect SQLAlchemy 2.0 async support
    if "sqlalchemy.ext.asyncio" in sys.modules:
        result["async_orm"] = True

        try:
            from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine
            result["async_engine"] = True
        except ImportError:
            pass

    # Detect session class
    if "sqlalchemy.orm" in sys.modules:
        try:
            from sqlalchemy.orm import Session
            result["session_class"] = "Session"
        except ImportError:
            pass

    # Detect scoped session
    if "sqlalchemy.orm.scoping" in sys.modules:
        result["scoped_session"] = True

    return result


def detect_orm_patterns(code_ast: Optional[Any] = None) -> List[Dict[str, Any]]:
    """Detect potentially unstable ORM patterns.

    This is a static analysis helper that can be used during capture
    to warn about patterns that may cause non-determinism.

    Returns list of detected patterns with stability warnings.
    """
    patterns_detected: List[Dict[str, Any]] = []

    # For now, just check if ORM is being used heavily
    if "sqlalchemy.orm" in sys.modules:
        patterns_detected.append({
            "pattern": "orm_session",
            "warning": "SQLAlchemy ORM Session detected - queries without ORDER BY may have non-deterministic ordering",
            "severity": "info",
        })

    if "sqlalchemy.orm.scoping" in sys.modules:
        patterns_detected.append({
            "pattern": "scoped_session",
            "warning": "Scoped sessions detected - ensure proper session cleanup between tests",
            "severity": "info",
        })

    return patterns_detected


def install_session_tracking() -> None:
    """Install session boundary tracking for SQLAlchemy.

    This patches Session.execute to record session state for debugging.
    """
    global _original_session_execute

    if "sqlalchemy.orm" not in sys.modules:
        return

    try:
        from sqlalchemy.orm import Session
    except ImportError:
        return

    if _original_session_execute is not None:
        return  # Already patched

    _original_session_execute = Session.execute

    def patched_execute(self, statement, *args, **kwargs):
        """Patched Session.execute that records session state."""
        recorder = _get_recorder()

        # Record session state before execution
        recorder.record_event({
            "type": "ORM_EXECUTE",
            "session_id": id(self),
            "in_transaction": self.in_transaction() if hasattr(self, "in_transaction") else None,
            "statement_type": type(statement).__name__,
            "is_active": self.is_active if hasattr(self, "is_active") else None,
        })

        return _original_session_execute(self, statement, *args, **kwargs)

    Session.execute = patched_execute


def install_async_session_tracking() -> None:
    """Install AsyncSession tracking for SQLAlchemy 2.0."""
    global _original_async_session_init

    if "sqlalchemy.ext.asyncio" not in sys.modules:
        return

    try:
        from sqlalchemy.ext.asyncio import AsyncSession
    except ImportError:
        return

    if _original_async_session_init is not None:
        return  # Already patched

    _original_async_session_init = AsyncSession.__init__

    def patched_init(self, *args, **kwargs):
        """Patched AsyncSession.__init__ that records creation."""
        recorder = _get_recorder()

        recorder.record_event({
            "type": "SQLALCHEMY_ASYNC_SESSION",
            "session_id": id(self),
            "warning": "AsyncSession detected - async ORM support is experimental",
        })

        return _original_async_session_init(self, *args, **kwargs)

    AsyncSession.__init__ = patched_init


def uninstall_session_tracking() -> None:
    """Restore original SQLAlchemy session methods."""
    global _original_session_execute, _original_async_session_init

    if _original_session_execute is not None:
        try:
            from sqlalchemy.orm import Session
            Session.execute = _original_session_execute
        except ImportError:
            pass
        _original_session_execute = None

    if _original_async_session_init is not None:
        try:
            from sqlalchemy.ext.asyncio import AsyncSession
            AsyncSession.__init__ = _original_async_session_init
        except ImportError:
            pass
        _original_async_session_init = None


def normalize_sqlalchemy_sql(sql: str) -> str:
    """Normalize SQLAlchemy-generated SQL for deterministic comparison.

    SQLAlchemy may generate SQL with:
    - Different column ordering in SELECT
    - Different parameter placeholder styles
    - Varying whitespace

    This function normalizes these variations.
    """
    import re

    # Normalize whitespace
    sql = " ".join(sql.split())

    # Normalize parameter placeholders
    # SQLAlchemy uses :param_1, :param_2, etc.
    # Normalize to :param_N
    sql = re.sub(r":(\w+)_\d+", r":\1_N", sql)

    # Sort column lists in SELECT (basic implementation)
    # This is a simplified version - full implementation would need SQL parsing

    return sql


def get_sqlalchemy_info() -> Dict[str, Any]:
    """Get comprehensive SQLAlchemy detection info for manifest."""
    info = detect_sqlalchemy()

    if info is None:
        return {
            "detected": False,
        }

    patterns = detect_orm_patterns()

    return {
        "detected": True,
        **info,
        "patterns_detected": patterns,
        "stability_notes": [
            "SQLAlchemy Core queries are captured via DBAPI (psycopg/psycopg2)",
            "ORM queries may have non-deterministic ordering without explicit ORDER BY",
            "Async ORM (SQLAlchemy 2.0) support is experimental",
        ] if info["orm_used"] else [
            "SQLAlchemy Core detected - queries captured via DBAPI",
        ],
    }


def install_all_sqlalchemy_patches() -> None:
    """Install all SQLAlchemy-related patches."""
    install_session_tracking()
    install_async_session_tracking()


def uninstall_all_sqlalchemy_patches() -> None:
    """Uninstall all SQLAlchemy-related patches."""
    uninstall_session_tracking()
