from passlib.context import CryptContext

import logging
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional

from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
from starlette import status

from src.apps.auth.models.sessions_model import Session as SessionModel
from src.apps.auth.schemas.session_schemas import SessionSchema
from src.apps.role_permission.models.permission_model import Permission
from src.apps.role_permission.services.permission_service import get_user_permissions
from src.apps.role_permission.services.role_service import get_user_role_slugs
from src.apps.user.models.user_model import Users
from src.apps.user.schemas.user_schemas import UserReturnSchema
from src.core.config import settings
from src.core.dependencies import get_db
from src.utils.enums import UserAccessLevel
from src.utils.helpers.auth import verify_access_token

logger = logging.getLogger(__name__)

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    Verify a password against a hash
    """
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """
    Hash a password for storing
    """
    return pwd_context.hash(password)


class AuthGuard(HTTPBearer):
    def __init__(self, auto_error: bool = True):
        super(AuthGuard, self).__init__(auto_error=auto_error)

    async def __call__(self, request: Request):
        credentials: HTTPAuthorizationCredentials = await super(AuthGuard, self).__call__(request)
        if credentials:
            if not credentials.scheme == "Bearer":
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Invalid authentication scheme.",
                )
            decoded_token = self.verify_jwt(credentials.credentials)
            if decoded_token is None:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Invalid or expired token.",
                )

            return credentials.credentials
        else:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Invalid authorization code.",
            )

    def verify_jwt(self, jwtoken: str) -> Optional[Dict]:
        payload = verify_access_token(jwtoken)
        return payload


class OptionalAuthGuard(HTTPBearer):
    def __init__(self):
        super(OptionalAuthGuard, self).__init__(auto_error=False)

    async def __call__(self, request: Request):
        credentials: HTTPAuthorizationCredentials = await super(OptionalAuthGuard, self).__call__(request)
        if not credentials:
            return None

        if not credentials.scheme == "Bearer":
            return None

        decoded_token = self.verify_jwt(credentials.credentials)
        if decoded_token is None:
            return None

        return credentials.credentials

    def verify_jwt(self, jwtoken: str) -> Optional[Dict]:
        payload = verify_access_token(jwtoken)
        return payload


async def get_active_session(
    token: str = Depends(AuthGuard()), db: Session = Depends(get_db)
) -> Optional[SessionSchema]:
    session = db.query(SessionModel).filter(SessionModel.token == token).first()
    if session is None:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Session timed out or does not exists.",
        )
    return session


async def get_current_user(
    token: str = Depends(AuthGuard()), db: Session = Depends(get_db)
) -> Optional[UserReturnSchema]:
    session = await get_active_session(token=token, db=db)
    auto_session_timeout = False
    decoded_token = verify_access_token(session.token)
    if not session.persist:
        if decoded_token is not None:
            decoded_user_data: Dict = decoded_token.get("user", {})
            user_record = db.query(Users).filter(Users.id == decoded_user_data.get("id")).first()
            if not user_record:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="User not found.",
                )

            # Check if user has super_admin role (previously checked via access_level)
            from src.apps.role_permission.services.role_service import (
                get_user_role_slugs,
            )

            user_roles = await get_user_role_slugs(db=db, user_id=user_record.user_id)
            is_super_admin = "super_admin" in user_roles

            # Use special expiration time for super_admin users
            expiry_time = (
                session.expires_at
                if is_super_admin
                else (session.refreshed_at if auto_session_timeout else session.expires_at)
            )
            token_expiry_threshold = datetime.now(timezone.utc)
            if not expiry_time or expiry_time.timestamp() < token_expiry_threshold.timestamp():
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Authentication token expired!",
                )

    if decoded_token is not None:
        decoded_user: Dict = decoded_token.get("user", dict())
        user_record = db.query(Users).filter(Users.id == decoded_user.get("id")).first()
        if not user_record:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="User not found.",
            )
        if not decoded_user.get("is_active", False):
            raise HTTPException(
                status_code=status.HTTP_423_LOCKED,
                detail="Account has been deactivated.",
            )

        if not decoded_user.get("is_verified", False):
            raise HTTPException(
                status_code=status.HTTP_412_PRECONDITION_FAILED,
                detail="Account has not been verified.",
            )
        all_roles = getattr(user_record, "all_roles", [])
        # If user is a super admin, add all permissions to their super admin role
        if UserAccessLevel.SUPER_ADMIN.value in all_roles:
            all_permissions = db.query(Permission).filter(Permission.is_active == True).all()
            for role_entry in user_record.roles:
                if role_entry.role.slug == UserAccessLevel.SUPER_ADMIN.value:
                    role_entry.permissions = all_permissions
                    break

        return UserReturnSchema(**user_record._asdict(), avatar=user_record.avatar, all_roles=user_record.all_roles)

    raise HTTPException(
        status_code=status.HTTP_403_FORBIDDEN,
        detail="Session timed out or does not exists.",
    )


class AdministratorRoleRequiredGuard:
    def __init__(self, allowed_roles: Optional[list[str]] = None):
        self.required_permissions = []
        if allowed_roles:
            for role in allowed_roles:
                try:
                    self.required_permissions.append(UserAccessLevel(role).value)
                except ValueError:
                    if hasattr(UserAccessLevel, role.upper()):
                        self.required_permissions.append(getattr(UserAccessLevel, role.upper()).value)
        else:
            self.required_permissions = [UserAccessLevel.SUPER_ADMIN.value]

    async def __call__(
        self,
        user: Users = Depends(get_current_user),
        db: Session = Depends(get_db),
    ) -> bool:
        # Get user permissions from the role-permission system
        user_permissions = await get_user_permissions(db=db, user_id=user.user_id)

        # Check if user has any of the required permissions
        for permission in self.required_permissions:
            if permission in user_permissions:
                return True

        raise HTTPException(
            status_code=status.HTTP_412_PRECONDITION_FAILED,
            detail="You do not have the required administrative privileges.",
        )


async def admin_required_user(
    current_user: Users = Depends(get_current_user),
    db: Session = Depends(get_db),
) -> UserReturnSchema:
    user_roles = await get_user_role_slugs(db=db, user_id=current_user.user_id)
    if not any(role in user_roles for role in ["admin", "super_admin"]):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have permission to access this resource.",
        )

    return current_user


async def optionally_authenticated_user(
    token: str = Depends(OptionalAuthGuard()), db: Session = Depends(get_db)
) -> Optional[UserReturnSchema]:
    """
    Similar to get_current_user but doesn't block access if no token is provided.
    If a token is provided, it operates the same as get_current_user.

    Returns:
        UserReturnSchema: If a valid token was provided
        None: If no token was provided or the token was invalid
    """
    if not token:
        return None

    try:
        session = db.query(SessionModel).filter(SessionModel.token == token).first()
        if session is None:
            return None

        auto_session_timeout = False
        decoded_token = verify_access_token(session.token)

        if not session.persist:
            if decoded_token is not None:
                decoded_user_data: Dict = decoded_token.get("user", {})
                user_record = db.query(Users).filter(Users.id == decoded_user_data.get("id")).first()
                if not user_record:
                    return None

                # Check if user has super_admin role
                user_roles = await get_user_role_slugs(db=db, user_id=user_record.user_id)
                is_super_admin = "super_admin" in user_roles

                # Use special expiration time for super_admin users
                expiry_time = (
                    session.expires_at
                    if is_super_admin
                    else (session.refreshed_at if auto_session_timeout else session.expires_at)
                )
                token_expiry_threshold = datetime.now(timezone.utc)
                if not expiry_time or expiry_time.timestamp() < token_expiry_threshold.timestamp():
                    return None

        if decoded_token is not None:
            decoded_user: Dict = decoded_token.get("user", dict())
            user_record = db.query(Users).filter(Users.id == decoded_user.get("id")).first()

            if not user_record:
                return None

            if not decoded_user.get("is_active", False):
                return None

            if not decoded_user.get("is_verified", False):
                return None

            all_roles = getattr(user_record, "all_roles", [])
            # If user is a super admin, add all permissions to their super admin role
            if UserAccessLevel.SUPER_ADMIN.value in all_roles:
                all_permissions = db.query(Permission).filter(Permission.is_active == True).all()
                for role_entry in user_record.roles:
                    if role_entry.role.slug == UserAccessLevel.SUPER_ADMIN.value:
                        role_entry.permissions = all_permissions
                        break

            return UserReturnSchema(**user_record._asdict(), avatar=user_record.avatar, all_roles=user_record.all_roles)

        return None
    except Exception as e:
        logger.error(f"Error in optionally_authenticated_user: {str(e)}")
        return None