import logging
from functools import wraps
from typing import Any, Callable, Generator

from fastapi import Cookie, Depends
from sqlalchemy.orm import Session as OrmSession

from app.core.database import SessionLocal
from app.core.errors import AppError
from app.core.security import is_session_expired
from app.i18n.loader import t
from app.models.catalog import Session as SessionModel, UserAccount, ProjectRegistry, ProjectMember

logger = logging.getLogger(__name__)


def get_db() -> Generator[OrmSession, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


def get_current_user(
    session_token: str | None = Cookie(default=None, alias="session"),
    db: OrmSession = Depends(get_db),
) -> UserAccount:
    if not session_token:
        raise AppError(
            code="auth.not_authenticated",
            message=t("auth.not_authenticated"),
            status_code=401,
        )

    session = db.query(SessionModel).filter(SessionModel.id == session_token).first()
    if not session:
        raise AppError(
            code="auth.not_authenticated",
            message=t("auth.not_authenticated"),
            status_code=401,
        )

    if is_session_expired(session.expires_at):
        db.delete(session)
        db.commit()
        raise AppError(
            code="auth.session_expired",
            message=t("auth.session_expired"),
            status_code=401,
        )

    user = db.query(UserAccount).filter(UserAccount.id == session.user_id).first()
    if not user:
        raise AppError(
            code="user.not_found",
            message=t("user.not_found"),
            status_code=404,
        )

    if not user.is_active:
        raise AppError(
            code="user.deactivated",
            message=t("user.deactivated"),
            status_code=403,
        )

    return user


def require_admin(current_user: UserAccount = Depends(get_current_user)) -> UserAccount:
    if current_user.role != "admin":
        raise AppError(
            code="permission.admin_required",
            message=t("permission.admin_required"),
            status_code=403,
        )
    return current_user


def verify_project_access(
    project_id: str,
    db: OrmSession = Depends(get_db),
    user: UserAccount = Depends(get_current_user),
) -> str:
    """Verify the user has access to the project and return the project_id.

    Replaces the old get_project_db() dependency.
    """
    project = (
        db.query(ProjectRegistry)
        .filter(ProjectRegistry.id == project_id, ProjectRegistry.status != "deleted")
        .first()
    )
    if not project:
        raise AppError(
            code="project.not_found",
            message=t("project.not_found"),
            status_code=404,
        )

    # Check access
    if user.role != "admin":
        member = (
            db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id, ProjectMember.user_id == user.id)
            .first()
        )
        if not member:
            raise AppError(
                code="project.access_denied",
                message=t("project.access_denied"),
                status_code=403,
            )

    return project_id


# ─────────────────────────────────────────────────────────
# Phase 2.5 — API 엔드포인트 공용 데코레이터
# ─────────────────────────────────────────────────────────


def api_endpoint(func: Callable) -> Callable:
    """모든 API 엔드포인트에 적용 가능한 공용 데코레이터.

    - dict 응답이면 `warnings` 키를 자동으로 ensure (없으면 빈 리스트).
    - `AppError`는 그대로 재전파 (핸들러에서 JSON 응답 변환).
    - 기타 예상치 못한 예외는 WARNING 로그 후 AppError(code='internal_error', 500)로 정규화.

    적용: `@router.post(...)` 아래에 `@api_endpoint` 추가.
    신규 endpoint는 필수, 기존은 점진 적용.

    참조: docs/architecture-refactor-final/02-final-roadmap.md §Phase 2.5
    """
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        try:
            result = func(*args, **kwargs)
        except AppError:
            raise
        except Exception as exc:
            logger.exception("Unhandled exception in API endpoint %s", func.__name__)
            raise AppError(
                code="internal_error",
                message=f"내부 오류: {exc}",
                status_code=500,
            ) from exc

        if isinstance(result, dict):
            result.setdefault("warnings", [])
        return result

    return wrapper
