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


def require_project_write(project_id: str, db: OrmSession, user: UserAccount) -> None:
    """이 사람이 **써도 되나** — owner 또는 admin 만. ★읽기 권한으로 파이프라인 입력을 못 바꾼다.

    ★★★(Codex BLOCK 2026-09-02) `verify_project_access` 는 **member 인지만** 본다.
    고증 판정(POST)과 좌표 선언(PUT) 은 다음 유료 조사의 좌표와 config hash 를 바꾸는
    **쓰기**라 같은 문을 지난다 — 규칙은 여기 한 곳."""
    verify_project_access(project_id, db=db, user=user)
    if getattr(user, "role", "") == "admin":
        return
    member = (db.query(ProjectMember)
              .filter(ProjectMember.project_id == project_id,
                      ProjectMember.user_id == user.id).first())
    if member is None or str(getattr(member, "role", "")) != "owner":
        raise AppError(
            code="project.write_denied",
            message=("파이프라인 입력을 바꾸는 쓰기는 **owner 나 admin** 만 한다 — "
                     "읽기 권한으로는 못 바꾼다"),
            status_code=403)
