import secrets
import socket
import uuid
from datetime import date, datetime, timedelta
from typing import Dict, List, Tuple

from fastapi import Depends, HTTPException, status
from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import Session
from starlette.requests import Request

from src.apps.auth.models.sessions_model import Session as SessionModel
from src.apps.auth.schemas.auth_schemas import LoginRequest
from src.apps.auth.schemas.session_schemas import SessionCreate
from src.apps.role_permission.models.role_model import Role
from src.apps.role_permission.models.user_permission_model import UserPermissions
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, UserSchema
from src.core.config import settings
from src.core.exceptions import APIException
from src.utils.enums import UserAccessLevel
from src.utils.helpers.auth import create_access_token, verify_password
from src.utils.helpers.functions import (
    generate_secure_id,
    get_password_hash,
    get_site_settings_value,
)
from src.utils.ip_geo_tool import get_device_info, get_ip, get_ipinfo_data


async def get_user(db: Session, username: str):
    return db.query(Users).filter(Users.username == username).first()  # Correct query


async def create_user_session(
    request: Request,
    db: Session,
    user: Users,
    access_token: str,
    expires_at: datetime,
    keep_session: bool = False,
) -> SessionModel:
    user_agent_string = request.headers.get("user-agent", "")

    # ip_address = socket.gethostbyname(
    #     request.client.host,
    # )

    # Get real client IP
    ip_address = get_ip(request)

    # For testing you forced it here (remove this in prod)
    ip_info = get_ipinfo_data(ip_address) or {}
    device_info = get_device_info(request) or {}

    referer = request.headers.get("referer", "/")

    session_id = generate_secure_id(db, instance=SessionModel, prepend="se", length=20)
    obj_in_data = jsonable_encoder(
        SessionCreate(
            session_id=session_id,
            user_id=user.id,
            token=access_token,
            expires_at=expires_at,
            refreshed_at=datetime.now(),
            created_at=datetime.now(),
            persist=keep_session,
            ip_addr=ip_address,
            user_agent=user_agent_string,
            referer=referer,
            device=device_info.get("device"),
            os=device_info.get("os"),
            location=ip_info.get("loc"),
            city=ip_info.get("city"),
            region=ip_info.get("region"),
            country=ip_info.get("country"),
            loc=ip_info.get("loc"),
            postal=ip_info.get("postal"),
            timezone=ip_info.get("timezone"),
            browser=device_info.get("browser"),
            is_mobile=device_info.get("is_mobile"),
        ),
        exclude_unset=True,
    )

    result = SessionModel(**obj_in_data)
    db.add(result)
    db.commit()
    db.refresh(result)

    return result


async def login_user(db: Session, payload: LoginRequest, request: Request) -> Tuple[str, datetime]:
    # Check if input is email or username
    identifier = payload.identifier

    # First find the user without access_level filtering
    if "@" in identifier:
        user = (
            db.query(Users)
            .filter(
                Users.email == identifier,
                Users.deleted_at == None,
                Users.is_active == True,
            )
            .first()
        )
    elif identifier.isdigit() or (identifier.startswith("+") and identifier[1:].isdigit()):
        # Identifier is phone number (digits only or +digits)
        user = (
            db.query(Users)
            .filter(
                Users.phone == identifier,
                Users.deleted_at == None,
                Users.is_active == True,
            )
            .first()
        )
    else:
        user = (
            db.query(Users)
            .filter(
                Users.username == identifier,
                Users.deleted_at == None,
                Users.is_active == True,
            )
            .first()
        )

    if not user:
        # raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Invalid credentials",
        )
    if not verify_password(payload.password, user.password):
        # HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid password")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Invalid password",
        )

    if not user.is_active:
        # raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account inactive")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Account inactive",
        )

    if not user.is_verified:
        # raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account is not verified")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Account is not verified",
        )

    user_dict = user._asdict(include_properties=False)

    if isinstance(user_dict.get("dob"), date):
        user_dict["dob"] = user_dict["dob"].isoformat()

    current_user = UserSchema(**user_dict).dict()

    # Create access token
    access_token, expires_at = create_access_token(
        data={"user": current_user}, expires_delta=timedelta(minutes=int(settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES))
    )

    # Create session record
    session = await create_user_session(
        request=request,
        db=db,
        user=user,
        access_token=access_token,
        expires_at=expires_at,
        keep_session=payload.keep_session,
    )

    return access_token, expires_at


async def logout_user(db: Session, token: str, other_sessions: bool = False) -> Dict[str, bool]:
    """
    Log out a user by invalidating their session token

    Args:
        db: Database session
        token: The access token to invalidate

    Returns:
        Dict with success status
    """
    if other_sessions:
        active_session = db.query(SessionModel).filter(SessionModel.token == token).first()

        if not active_session:
            # raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Active session not found")
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_400_BAD_REQUEST,
                message="Active session not found",
            )

        # 2. Delete all other sessions for the user
        # Expire all other active sessions for the user except the current one
        # Only update sessions that are not already expired
        db.query(SessionModel).filter(
            SessionModel.user_id == active_session.user_id,
            SessionModel.id != active_session.id,
            SessionModel.expires_at > datetime.now(),
        ).update({SessionModel.expires_at: datetime.now()}, synchronize_session=False)

        db.commit()
    else:
        # Find the session with this token
        session = db.query(SessionModel).filter(SessionModel.token == token).first()

        if not session:
            # If no session is found, it's already logged out or token is invalid
            return {"success": True}

        # Mark the session as expired by setting expiry time to now
        session.expires_at = datetime.now()

        # Save changes to the database
        db.commit()

    return {"success": True}
