# backend/app/routers/auth.py — 로그인/로그아웃/세션/내보내기/토큰 (phase-15)
import uuid
from datetime import UTC, datetime, timedelta

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import StreamingResponse
from sqlmodel import Session, select

from ..auth.deps import current_user
from ..auth.password import verify_password
from ..auth.tokens import (
    hash_token,
    new_api_token,
    new_session_id,
    sign_session_id,
    unsign_session_id,
)
from ..config import get_settings
from ..db import get_session
from ..models import ApiToken, AuthSession, Person, UserCredential
from ..schemas import LoginIn, MeOut, ProfileUpdateIn, TokenInfoOut, TokenOut
from ..security.audit import audit
from ..services.export_service import build_export

router = APIRouter()

_LOGIN_FAIL = "이메일 또는 비밀번호가 올바르지 않아요"


def _me(session: Session, user: Person) -> MeOut:
    cred = session.exec(select(UserCredential).where(UserCredential.user_id == user.id)).first()
    return MeOut(
        id=user.id,
        name=user.name,
        initial=user.initial,
        email=cred.email if cred else (user.email or ""),
        role=cred.role if cred else (user.role or "member"),
    )


@router.post("/auth/login", response_model=MeOut)
def login(body: LoginIn, request: Request, response: Response, s: Session = Depends(get_session)):
    st = get_settings()
    ip = request.client.host if request.client else None
    cred = s.exec(select(UserCredential).where(UserCredential.email == body.email)).first()
    if not cred or not cred.is_active or not verify_password(body.password, cred.password_hash):
        audit(s, cred.user_id if cred else None, "login.fail", ip=ip, detail=body.email)
        raise HTTPException(401, _LOGIN_FAIL)
    now = datetime.now(UTC)
    sid = new_session_id()
    s.add(
        AuthSession(
            id=sid,
            user_id=cred.user_id,
            created_at=now,
            expires_at=now + timedelta(seconds=st.session_ttl_s),
            user_agent=request.headers.get("User-Agent"),
            ip=ip,
        )
    )
    cred.last_login_at = now
    s.add(cred)
    s.commit()
    response.set_cookie(
        st.session_cookie_name,
        sign_session_id(sid),
        max_age=st.session_ttl_s,
        httponly=True,
        secure=st.cookie_secure,
        samesite="lax",
    )
    audit(s, cred.user_id, "login", ip=ip)
    user = s.get(Person, cred.user_id)
    return _me(s, user)


@router.post("/auth/logout")
def logout(request: Request, response: Response, s: Session = Depends(get_session)):
    st = get_settings()
    raw = request.cookies.get(st.session_cookie_name)
    uid = None
    if raw:
        sid = unsign_session_id(raw)
        if sid:
            sess = s.get(AuthSession, sid)
            if sess:
                sess.revoked = True
                uid = sess.user_id
                s.add(sess)
                s.commit()
    response.delete_cookie(st.session_cookie_name)
    audit(s, uid, "logout")
    return {"ok": True}


@router.get("/me", response_model=MeOut)
def me(user: Person = Depends(current_user), s: Session = Depends(get_session)):
    return _me(s, user)


@router.patch("/me", response_model=MeOut)
def update_me(
    body: ProfileUpdateIn,
    user: Person = Depends(current_user),
    s: Session = Depends(get_session),
):
    """표시 이름/역할 갱신. role 은 자격(UserCredential)이 있으면 거기에, 없으면 Person 에 저장."""
    if body.name is not None and body.name.strip():
        user.name = body.name.strip()
        user.initial = body.name.strip()[0]
    cred = s.exec(select(UserCredential).where(UserCredential.user_id == user.id)).first()
    if body.role is not None:
        role = body.role.strip()
        if cred:
            cred.role = role
            s.add(cred)
        else:
            user.role = role
    s.add(user)
    s.commit()
    s.refresh(user)
    audit(s, user.id, "profile.update")
    return _me(s, user)


@router.get("/me/export")
def export_me(user: Person = Depends(current_user), s: Session = Depends(get_session)):
    data = build_export(s, user)
    audit(s, user.id, "export")
    return StreamingResponse(
        iter([data]),
        media_type="application/zip",
        headers={"Content-Disposition": f'attachment; filename="ari-export-{user.id}.zip"'},
    )


@router.get("/me/tokens", response_model=list[TokenInfoOut])
def list_tokens(user: Person = Depends(current_user), s: Session = Depends(get_session)):
    rows = s.exec(
        select(ApiToken).where(ApiToken.user_id == user.id).order_by(ApiToken.created_at.desc())
    ).all()
    return [
        TokenInfoOut(
            id=t.id,
            name=t.name,
            created_at=t.created_at.isoformat(),
            last_used_at=t.last_used_at.isoformat() if t.last_used_at else None,
            revoked=t.revoked,
        )
        for t in rows
    ]


@router.post("/me/tokens", response_model=TokenOut)
def create_token(
    name: str = "API 토큰", user: Person = Depends(current_user), s: Session = Depends(get_session)
):
    plain = new_api_token()
    tid = "tok-" + uuid.uuid4().hex[:8]
    s.add(ApiToken(id=tid, user_id=user.id, name=name, token_hash=hash_token(plain)))
    s.commit()
    audit(s, user.id, "api_token.create", target=tid)
    return TokenOut(id=tid, name=name, token=plain, created_at=datetime.now(UTC).isoformat())


@router.delete("/me/tokens/{tid}")
def revoke_token(tid: str, user: Person = Depends(current_user), s: Session = Depends(get_session)):
    tok = s.get(ApiToken, tid)
    if not tok or tok.user_id != user.id:
        raise HTTPException(404, "not found")
    tok.revoked = True
    s.add(tok)
    s.commit()
    audit(s, user.id, "api_token.revoke", target=tid)
    return {"ok": True}
