"""Nexus — FastAPI Dependency Injection
Wires Repository implementations → Service instances → API route handlers.
All DI occurs here; routes never import infrastructure directly.

D7 Fix: Service factories now read session from request.state.db (set by
DbSessionMiddleware) instead of creating unclosed AsyncSessionLocal() instances.
This fixes the P0 session leak bug where 4 service factories never closed sessions.
"""

from typing import AsyncGenerator

from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession

from server.infrastructure.database.session import AsyncSessionLocal
from server.infrastructure.database.server_repo import ServerRepositoryImpl
from server.infrastructure.database.sync_log_repo import SyncLogRepositoryImpl
from server.infrastructure.database.script_repo import ScriptRepositoryImpl, ScriptExecutionRepositoryImpl
from server.infrastructure.database.db_credential_repo import DbCredentialRepositoryImpl
from server.infrastructure.database.admin_repo import AdminRepositoryImpl, LoginAttemptRepositoryImpl
from server.infrastructure.database.setting_repo import SettingRepositoryImpl
from server.infrastructure.database.audit_log_repo import AuditLogRepositoryImpl
from server.infrastructure.database.push_schedule_repo import PushScheduleRepositoryImpl, PushRetryJobRepositoryImpl
from server.infrastructure.database.password_preset_repo import PasswordPresetRepositoryImpl

from server.application.services.server_service import ServerService
from server.application.services.script_service import ScriptService
from server.application.services.auth_service import AuthService
from server.application.services.sync_service import SyncService


async def get_db(request: Request) -> AsyncGenerator[AsyncSession, None]:
    """Yield DB session from middleware (request.state.db).

    DbSessionMiddleware ensures every /api/ request has a session.
    This dependency simply yields it so routes can use Depends(get_db).
    No double-session: one session per request, shared everywhere.
    """
    session = getattr(request.state, "db", None)
    if session is not None:
        yield session
    else:
        # Fallback for tests or edge cases without middleware
        session = AsyncSessionLocal()
        try:
            yield session
        finally:
            await session.close()


async def _get_request_session(request: Request) -> AsyncSession:
    """Get DB session from middleware (request.state.db).

    DbSessionMiddleware opens a session at the start of each API request
    and stores it in request.state.db. This ensures:
    1. Session is always properly closed after the request
    2. All repositories/services in the same request share the same session
    3. No more leaked sessions from service factories
    """
    session = getattr(request.state, "db", None)
    if session is None:
        # Fallback: if middleware didn't set session (shouldn't happen for /api/ routes)
        raise RuntimeError("DB session not found in request.state — DbSessionMiddleware may be missing")
    return session


# ── Repository Factories ──

def _server_repo(session: AsyncSession) -> ServerRepositoryImpl:
    return ServerRepositoryImpl(session)

def _sync_log_repo(session: AsyncSession) -> SyncLogRepositoryImpl:
    return SyncLogRepositoryImpl(session)

def _script_repo(session: AsyncSession) -> ScriptRepositoryImpl:
    return ScriptRepositoryImpl(session)

def _execution_repo(session: AsyncSession) -> ScriptExecutionRepositoryImpl:
    return ScriptExecutionRepositoryImpl(session)

def _credential_repo(session: AsyncSession) -> DbCredentialRepositoryImpl:
    return DbCredentialRepositoryImpl(session)

def _admin_repo(session: AsyncSession) -> AdminRepositoryImpl:
    return AdminRepositoryImpl(session)

def _attempt_repo(session: AsyncSession) -> LoginAttemptRepositoryImpl:
    return LoginAttemptRepositoryImpl(session)

def _setting_repo(session: AsyncSession) -> SettingRepositoryImpl:
    return SettingRepositoryImpl(session)

def _audit_repo(session: AsyncSession) -> AuditLogRepositoryImpl:
    return AuditLogRepositoryImpl(session)

def _schedule_repo(session: AsyncSession) -> PushScheduleRepositoryImpl:
    return PushScheduleRepositoryImpl(session)

def _retry_repo(session: AsyncSession) -> PushRetryJobRepositoryImpl:
    return PushRetryJobRepositoryImpl(session)

def _preset_repo(session: AsyncSession) -> PasswordPresetRepositoryImpl:
    return PasswordPresetRepositoryImpl(session)


# ── Service Factories (D7 fixed: use request.state.db) ──

async def get_server_service(request: Request) -> ServerService:
    session = await _get_request_session(request)
    return ServerService(
        server_repo=_server_repo(session),
        sync_log_repo=_sync_log_repo(session),
    )


async def get_script_service(request: Request) -> ScriptService:
    session = await _get_request_session(request)
    return ScriptService(
        script_repo=_script_repo(session),
        execution_repo=_execution_repo(session),
        credential_repo=_credential_repo(session),
        server_repo=_server_repo(session),
        audit_repo=_audit_repo(session),
    )


async def get_auth_service(request: Request) -> AuthService:
    session = await _get_request_session(request)
    return AuthService(
        admin_repo=_admin_repo(session),
        attempt_repo=_attempt_repo(session),
        audit_repo=_audit_repo(session),
    )


async def get_sync_service(request: Request) -> SyncService:
    session = await _get_request_session(request)
    return SyncService(
        server_repo=_server_repo(session),
        sync_log_repo=_sync_log_repo(session),
        audit_repo=_audit_repo(session),
        retry_repo=_retry_repo(session),
    )


# ── Command Safety Detection (P2-18) ──

import re
import logging

_logger = logging.getLogger("nexus.command_safety")

# Patterns that indicate highly dangerous commands
_DANGEROUS_PATTERNS = [
    (r"\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|-r[a-zA-Z]*\s+|.*--no-preserve-root\s+)/\s*$", "rm -rf / (全盘删除)"),
    (r"\bdd\s+.*of=/dev/[a-z]+\s", "dd 写入裸设备 (数据毁灭)"),
    (r":\(\)\{\s*:\|:&\s*\}", "Fork bomb (内存耗尽)"),
    (r"mkfs\.", "格式化文件系统"),
    (r">\s*/dev/sd[a-z]", "覆写磁盘设备"),
    (r"chmod\s+(-R\s+)?000\s+/", "根目录权限清零"),
    (r"chown\s+(-R\s+)?\S+\s+/", "根目录归属变更"),
]

_DANGEROUS_COMPILED = [(re.compile(p, re.IGNORECASE), desc) for p, desc in _DANGEROUS_PATTERNS]


def check_dangerous_command(command: str) -> list[str]:
    """Check a command for dangerous patterns.

    Returns a list of warning descriptions (empty if safe).
    This is a warning system, not a blocker — commands are still executed,
    but dangerous ones are logged at WARNING level for audit trail.
    """
    warnings = []
    for pattern, desc in _DANGEROUS_COMPILED:
        if pattern.search(command):
            warnings.append(desc)
    if warnings:
        _logger.warning(f"Dangerous command detected: {command[:200]} -> {warnings}")
    return warnings