"""
Customer authentication endpoints for the SaaS web application.

POST /auth/register  — create a new customer account
POST /auth/login      — exchange email+password for access+refresh tokens
POST /auth/refresh    — exchange a refresh token for a new access token
GET  /auth/me         — return the logged-in customer's profile

This is intentionally a self-contained, minimal auth flow (no OAuth/SSO,
no email verification flow implemented yet — is_verified exists as a
field for a future email-confirmation feature but isn't enforced at
login). That scope was confirmed with the project coordinator as
sufficient for initial SaaS launch; see docs/architecture.md.
"""

from datetime import datetime

from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.auth_tokens import (
    ACCESS_TOKEN_EXPIRE_MINUTES,
    create_access_token,
    create_refresh_token,
    decode_token,
    hash_password,
    verify_password,
)
from app.core.current_user import get_current_user
from app.core.exceptions import AppException, AuthenticationError
from app.core.logging_config import get_logger
from app.db.models import User
from app.db.session import get_db_session
from app.schemas.auth import (
    RefreshTokenRequest,
    TokenResponse,
    UserLoginRequest,
    UserProfileResponse,
    UserRegisterRequest,
)

router = APIRouter()
logger = get_logger(__name__)


class EmailAlreadyRegisteredError(AppException):
    status_code = 409
    error_code = "EMAIL_ALREADY_REGISTERED"


@router.post("/auth/register", response_model=TokenResponse, status_code=201)
async def register(
    payload: UserRegisterRequest,
    db: AsyncSession = Depends(get_db_session),
) -> TokenResponse:
    existing = await db.execute(select(User).where(User.email == payload.email.lower()))
    if existing.scalar_one_or_none() is not None:
        raise EmailAlreadyRegisteredError(
            message="An account with this email already exists. Try logging in instead."
        )

    user = User(
        email=payload.email.lower(),
        hashed_password=hash_password(payload.password),
        full_name=payload.full_name,
        organization=payload.organization,
    )
    db.add(user)
    await db.flush()  # populate user.id before using it in the tokens below
    await db.commit()

    logger.info(f"New user registered: user_id={user.id}")

    return TokenResponse(
        access_token=create_access_token(user.id, user.email),
        refresh_token=create_refresh_token(user.id),
        expires_in_minutes=ACCESS_TOKEN_EXPIRE_MINUTES,
    )


@router.post("/auth/login", response_model=TokenResponse)
async def login(
    payload: UserLoginRequest,
    db: AsyncSession = Depends(get_db_session),
) -> TokenResponse:
    result = await db.execute(select(User).where(User.email == payload.email.lower()))
    user = result.scalar_one_or_none()

    # Deliberately identical error for "no such user" and "wrong password" —
    # distinguishing them lets an attacker enumerate valid email addresses.
    if user is None or not verify_password(payload.password, user.hashed_password):
        raise AuthenticationError(message="Incorrect email or password")

    if not user.is_active:
        raise AuthenticationError(message="This account has been deactivated. Contact support.")

    # Store a naive UTC datetime to match the DB column type (TIMESTAMP without timezone)
    user.last_login_at = datetime.utcnow()
    await db.commit()

    return TokenResponse(
        access_token=create_access_token(user.id, user.email),
        refresh_token=create_refresh_token(user.id),
        expires_in_minutes=ACCESS_TOKEN_EXPIRE_MINUTES,
    )


@router.post("/auth/refresh", response_model=TokenResponse)
async def refresh_token(
    payload: RefreshTokenRequest,
    db: AsyncSession = Depends(get_db_session),
) -> TokenResponse:
    decoded = decode_token(payload.refresh_token, expected_type="refresh")
    user_id = decoded.get("sub")

    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()

    if user is None or not user.is_active:
        raise AuthenticationError(message="Account no longer valid. Please log in again.")

    return TokenResponse(
        access_token=create_access_token(user.id, user.email),
        refresh_token=create_refresh_token(user.id),  # rotate the refresh token too
        expires_in_minutes=ACCESS_TOKEN_EXPIRE_MINUTES,
    )


@router.get("/auth/me", response_model=UserProfileResponse)
async def get_profile(current_user: User = Depends(get_current_user)) -> UserProfileResponse:
    return UserProfileResponse(
        id=current_user.id,
        email=current_user.email,
        full_name=current_user.full_name,
        organization=current_user.organization,
        is_verified=current_user.is_verified,
        created_at=current_user.created_at.isoformat(),
    )
