from datetime import datetime, timedelta

import bcrypt
from fastapi import Depends, HTTPException, status, Request, Response
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from starlette.middleware.base import BaseHTTPMiddleware

from app.config import settings
from app.database import get_db
from app.models.user import User

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))


def get_password_hash(password: str) -> str:
    return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire, "iat": datetime.utcnow().timestamp()})
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)


def decode_token(token: str):
    """Decode JWT token and return payload, or None if invalid."""
    try:
        return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
    except JWTError:
        return None


def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db),
) -> User:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = db.query(User).filter(User.email == email.lower().strip()).first()
    if user is None:
        raise credentials_exception

    # Block unverified users from all protected endpoints
    from app.models.email_verification import EmailVerification
    verification = db.query(EmailVerification).filter(EmailVerification.user_id == user.id).first()
    if verification and verification.verified_at is None:
        raise HTTPException(
            status_code=403,
            detail="Email not verified. Please verify your email before using the app.",
            headers={"X-Unverified": "true"},
        )

    return user


def require_admin(current_user: User = Depends(get_current_user)) -> User:
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Admin access required")
    return current_user


def require_moderator(current_user: User = Depends(get_current_user)) -> User:
    if not current_user.is_moderator:
        raise HTTPException(status_code=403, detail="Moderator access required")
    return current_user


class TokenRefreshMiddleware(BaseHTTPMiddleware):
    """Middleware to refresh JWT token if it's older than 12 hours or expires soon (sliding expiration)."""

    async def dispatch(self, request: Request, call_next):
        # Extract token from Authorization header
        auth_header = request.headers.get("authorization", "")
        new_token = None

        if auth_header.startswith("Bearer "):
            token = auth_header[7:]
            payload = decode_token(token)
            if payload:
                iat = payload.get("iat")
                exp = payload.get("exp")
                now = datetime.utcnow().timestamp()

                if iat and exp:
                    age_hours = (now - iat) / 3600
                    time_until_expiry_hours = (exp - now) / 3600

                    # Refresh if: token > 12 hours old OR will expire in < 1 hour
                    if age_hours > 12 or time_until_expiry_hours < 1:
                        email = payload.get("sub")
                        if email:
                            new_token = create_access_token({"sub": email})

        response = await call_next(request)

        # Add new token to response headers if generated
        if new_token:
            response.headers["X-New-Token"] = new_token

        return response


def check_rate_limit(key: str, max_calls: int, window_seconds: int, detail: str, user=None):
    """Generic Redis-backed rate limiter. Admins and unthrottled users are always exempt.
    Degrades gracefully if Redis is down."""
    # Admins, moderators, and explicitly unthrottled users bypass all rate limits
    if user is not None:
        if getattr(user, 'role', '') in ('admin', 'moderator') or getattr(user, 'is_unthrottled', 0):
            return
    try:
        import redis as redis_lib
        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True, socket_connect_timeout=1)
        count = r.incr(key)
        if count == 1:
            r.expire(key, window_seconds)
        if count > max_calls:
            raise HTTPException(status_code=429, detail=detail)
    except HTTPException:
        raise
    except Exception:
        pass  # Redis unavailable — fail open rather than blocking users
