from app.models.friend_relationship import FriendRelationship
from app.models.friend_request import FriendRequest
from app.models.user import User

from app.extensions import db, redis_controller

import redis_lock

class FriendExceptions():
    class AlreadyFriends(Exception):
        pass
    class CannotFriendSelf(Exception):
        pass
    class RedisLockAcquireError(Exception):
        pass
    class UserRateLimited(Exception):
        pass
    class UserNotFriends(Exception):
        pass
    class FriendIsDisabled(Exception):
        pass
    class RecipientHasTooManyFriends(Exception):
        pass
    class SenderHasTooManyFriends(Exception):
        pass

def get_friend_count(
    user : User
) -> int:
    """
        :param user : User : The user

        :return int : The amount of friends the user has
    """

    assert isinstance(user, User), f"Expected user to be of type User, got {user.__class__}"

    return FriendRelationship.query.filter(
        (FriendRelationship.user_id == user.id) | (FriendRelationship.friend_id == user.id)
    ).count()

def get_friend_relationship(
    user1 : User,
    user2 : User
) -> FriendRelationship | None:
    """
        :param user1 : User : The first user
        :param user2 : User : The second

        :return FriendRelationship | None : The friend relationship or None
    """

    assert isinstance(user1, User), f"Expected user1 to be of type User, got {user1.__class__.__name__}"
    assert isinstance(user2, User), f"Expected user2 to be of type User, got {user2.__class__}"

    return FriendRelationship.query.filter(
        (FriendRelationship.user_id == user1.id and FriendRelationship.friend_id == user2.id) |
        (FriendRelationship.user_id == user2.id and FriendRelationship.friend_id == user1.id)
    ).first()

def create_friend_relationship(
    user1 : User,
    user2 : User,
) -> FriendRelationship:
    """
        :param user1 : User : The first user
        :param user2 : User : The second

        :return FriendRelationship : The friend relationship
    """

    assert isinstance(user1, User), f"Expected user1 to be of type User, got {user1.__class__.__name__}"
    assert isinstance(user2, User), f"Expected user2 to be of type User, got {user2.__class__}"

    FirstUser = user1 if user1.id < user2.id else user2
    SecondUser = user2 if user1.id < user2.id else user1

    try:
        with redis_lock.Lock( redis_client = redis_controller, name = f"services:friends:create_friend_relationship:{FirstUser.id}:{SecondUser.id}", expire = 10 ):
            if get_friend_relationship(user1, user2) is not None:
                raise FriendExceptions.AlreadyFriends

            friendRelationship = FriendRelationship(
                user_id = user1.id,
                friend_id = user2.id
            )

            db.session.add(friendRelationship)
            db.session.commit()

            return friendRelationship
    except AssertionError:
        raise FriendExceptions.RedisLockAcquireError

def remove_friend_relationship(
    user1 : User,
    user2 : User
) -> None:
    """
        :param user1 : User : The first user
        :param user2 : User : The second

        :return None
    """

    assert isinstance(user1, User), f"Expected user1 to be of type User, got {user1.__class__}"
    assert isinstance(user2, User), f"Expected user2 to be of type User, got {user2.__class__}"

    FirstUser = user1 if user1.id < user2.id else user2
    SecondUser = user2 if user1.id < user2.id else user1

    try:
        with redis_lock.Lock( redis_client = redis_controller, name = f"services:friends:remove_friend_relationship:{FirstUser.id}:{SecondUser.id}", expire = 10 ):
            friendRelationship = get_friend_relationship(user1, user2)
            if friendRelationship is None:
                raise FriendExceptions.UserNotFriends

            db.session.delete(friendRelationship)
            db.session.commit()

            return None
    except AssertionError:
        raise FriendExceptions.RedisLockAcquireError

def send_friend_request(
    sender_user : User,
    recipient_user : User
) -> FriendRequest | FriendRelationship:
    """
        :param sender_user : User : The user sending the friend request
        :param recipient_user : User : The user receiving the friend request

        :return FriendRequest | FriendRelationship : The friend request or friend relationship if there is already an existing friend request from the recipient user
    """

    assert isinstance(sender_user, User), f"Expected user1 to be of type User, got {sender_user.__class__}"
    assert isinstance(recipient_user, User), f"Expected user2 to be of type User, got {recipient_user.__class__}"

    FirstUser = sender_user if sender_user.id < recipient_user.id else recipient_user
    SecondUser = recipient_user if sender_user.id < recipient_user.id else sender_user

    try:
        with redis_lock.Lock( redis_client = redis_controller, name = f"services:friends:send_friend_request:{FirstUser.id}:{SecondUser.id}", expire = 10 ):
            if get_friend_relationship(sender_user, recipient_user) is not None:
                raise FriendExceptions.AlreadyFriends
            
            if sender_user.id == recipient_user.id:
                raise FriendExceptions.CannotFriendSelf
            
            if get_friend_count(recipient_user) >= 200:
                raise FriendExceptions.RecipientHasTooManyFriends
            if get_friend_count(sender_user) >= 200:
                raise FriendExceptions.SenderHasTooManyFriends
            
            if redis_controller.get(f"rate_limit:friends:send_friend_request:{sender_user.id}") is not None:
                raise FriendExceptions.UserRateLimited
            redis_controller.set(f"rate_limit:friends:send_friend_request:{sender_user.id}", "1", ex = 3)
            
            otherFriendRequest = FriendRequest.query.filter_by(requester_id = recipient_user.id, requestee_id = sender_user.id).first()
            if otherFriendRequest is not None:
                db.session.delete(otherFriendRequest)
                db.session.commit()

                return create_friend_relationship( user1 = sender_user, user2 = recipient_user)
            
            friendRequest = FriendRequest.query.filter_by(requester_id = sender_user.id, requestee_id = recipient_user.id).first()
            if friendRequest is not None:
                return friendRequest
            
            friendRequest = FriendRequest(
                requester_id = sender_user.id,
                requestee_id = recipient_user.id
            )
            db.session.add(friendRequest)
            db.session.commit()

            return friendRequest
    except AssertionError:
        raise FriendExceptions.RedisLockAcquireError
    
def decline_friend_request(
    sender_user : User,
    recipient_user : User
) -> None:
    """
        :param sender_user : User : The user sending the friend request
        :param recipient_user : User : The user receiving the friend request

        :return None
    """

    assert isinstance(sender_user, User), f"Expected user1 to be of type User, got {sender_user.__class__}"
    assert isinstance(recipient_user, User), f"Expected user2 to be of type User, got {recipient_user.__class__}"

    friendRequest = FriendRequest.query.filter_by(requester_id = sender_user.id, requestee_id = recipient_user.id).first()
    if friendRequest is not None:
        db.session.delete(friendRequest)
        db.session.commit()
    
    return None