"""
Password hashing and JWT token utilities.

This is a separate authentication mechanism from app/core/security.py's
API-key auth. API keys identify a calling service/integration with a
long-lived static secret; JWTs here identify an individual logged-in
customer with a short-lived, expiring token — the standard pattern for
a user-facing SaaS login, not machine-to-machine API access.

Password hashing uses bcrypt via passlib — never store or compare
plaintext passwords anywhere in this codebase.
"""

from datetime import datetime, timedelta, timezone
from typing import Any

from jose import JWTError, jwt
from passlib.context import CryptContext

from app.core.config import get_settings
from app.core.exceptions import AuthenticationError

settings = get_settings()

# bcrypt is intentionally slow (by design, to resist brute force) —
# this is the industry-standard choice for password hashing, not a
# performance oversight.
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

JWT_ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24  # 24 hours
REFRESH_TOKEN_EXPIRE_DAYS = 30


def hash_password(plain_password: str) -> str:
    return pwd_context.hash(plain_password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


def create_access_token(user_id: str, email: str) -> str:
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    payload = {
        "sub": user_id,
        "email": email,
        "type": "access",
        "exp": expire,
        "iat": datetime.now(timezone.utc),
    }
    return jwt.encode(payload, settings.SECRET_KEY, algorithm=JWT_ALGORITHM)


def create_refresh_token(user_id: str) -> str:
    expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    payload = {
        "sub": user_id,
        "type": "refresh",
        "exp": expire,
        "iat": datetime.now(timezone.utc),
    }
    return jwt.encode(payload, settings.SECRET_KEY, algorithm=JWT_ALGORITHM)


def decode_token(token: str, expected_type: str = "access") -> dict[str, Any]:
    """
    Raises AuthenticationError (not a raw JWTError) on any failure —
    expired, malformed, wrong signature, or wrong token type used in the
    wrong place (e.g. a refresh token presented where an access token
    is required). Callers should never need to know about python-jose's
    exception types directly.
    """
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[JWT_ALGORITHM])
    except JWTError as exc:
        raise AuthenticationError(message="Invalid or expired token") from exc

    if payload.get("type") != expected_type:
        raise AuthenticationError(message=f"Expected a {expected_type} token")

    return payload
