from django.db.models import Count, Max, Prefetch, Q
from django.utils import timezone

from jobs.models import JobApplication

from .models import Conversation, Message


def user_can_access_application(user, application):
    if user.is_platform_admin:
        return True
    if user.is_company and application.job.posted_by_id == user.id:
        return True
    if user.is_seeker and application.applicant_id == user.id:
        return True
    return False


def get_or_create_conversation(application):
    conversation, _created = Conversation.objects.get_or_create(application=application)
    return conversation


def get_or_create_direct_conversation(employer_user, seeker_user):
    conversation = Conversation.objects.filter(
        application__isnull=True,
        direct_employer=employer_user,
        direct_seeker=seeker_user,
    ).first()
    if conversation:
        return conversation
    return Conversation.objects.create(
        direct_employer=employer_user,
        direct_seeker=seeker_user,
    )


def get_user_conversations(user):
    if user.is_platform_admin:
        qs = Conversation.objects.all()
    elif user.is_company:
        qs = Conversation.objects.filter(
            Q(application__job__posted_by=user) | Q(direct_employer=user),
        )
    elif user.is_seeker:
        qs = Conversation.objects.filter(
            Q(application__applicant=user) | Q(direct_seeker=user),
        )
    else:
        return Conversation.objects.none()

    return qs.select_related(
        'application',
        'application__job',
        'application__applicant',
        'application__applicant__seeker_profile',
        'application__job__posted_by',
        'direct_employer',
        'direct_seeker',
        'direct_seeker__seeker_profile',
    ).annotate(
        unread_count=Count(
            'messages',
            filter=Q(messages__read_at__isnull=True) & ~Q(messages__sender=user),
        ),
        last_message_at=Max('messages__created_at'),
    ).order_by('-updated_at', '-last_message_at')


def get_conversation_for_user(user, pk):
    return get_user_conversations(user).filter(pk=pk).first()


class ConversationClosedError(Exception):
    """Raised when attempting to send a message on a closed conversation."""


def send_message(conversation, sender, body):
    if conversation.is_closed:
        raise ConversationClosedError('This conversation is closed.')
    message = Message.objects.create(
        conversation=conversation,
        sender=sender,
        body=body.strip(),
    )
    Conversation.objects.filter(pk=conversation.pk).update(updated_at=timezone.now())
    return message


def close_conversation(conversation, user):
    employer = conversation.employer
    if user.is_platform_admin or (user.is_company and employer and employer.id == user.id):
        pass
    else:
        return False
    if conversation.is_closed:
        return True
    Conversation.objects.filter(pk=conversation.pk).update(
        status=Conversation.Status.CLOSED,
        closed_at=timezone.now(),
        closed_by=user,
        updated_at=timezone.now(),
    )
    conversation.refresh_from_db()
    return True


def reopen_conversation(conversation, user):
    employer = conversation.employer
    if user.is_platform_admin or (user.is_company and employer and employer.id == user.id):
        pass
    else:
        return False
    if conversation.is_open:
        return True
    Conversation.objects.filter(pk=conversation.pk).update(
        status=Conversation.Status.OPEN,
        closed_at=None,
        closed_by=None,
        updated_at=timezone.now(),
    )
    conversation.refresh_from_db()
    return True


def mark_conversation_read(conversation, reader):
    Message.objects.filter(
        conversation=conversation,
        read_at__isnull=True,
    ).exclude(sender=reader).update(read_at=timezone.now())


def unread_message_count(user):
    if not user.is_authenticated:
        return 0
    if user.is_platform_admin:
        base = Message.objects.filter(read_at__isnull=True).exclude(sender=user)
    elif user.is_company:
        base = Message.objects.filter(
            Q(conversation__application__job__posted_by=user) | Q(conversation__direct_employer=user),
            read_at__isnull=True,
        ).exclude(sender=user)
    elif user.is_seeker:
        base = Message.objects.filter(
            Q(conversation__application__applicant=user) | Q(conversation__direct_seeker=user),
            read_at__isnull=True,
        ).exclude(sender=user)
    else:
        return 0
    return base.count()


def start_conversation_for_application(user, application_id):
    application = JobApplication.objects.select_related('job', 'applicant').get(pk=application_id)
    if not user_can_access_application(user, application):
        return None
    return get_or_create_conversation(application)


def start_direct_conversation_with_seeker(employer_user, seeker_user):
    if not seeker_user.is_seeker:
        return None
    if not (employer_user.is_company or employer_user.is_platform_admin):
        return None
    if employer_user.id == seeker_user.id:
        return None
    return get_or_create_direct_conversation(employer_user, seeker_user)


def get_application_messaging_context(user, application):
    """Chat state for embedding messaging in ATS application views."""
    if not user_can_access_application(user, application):
        return None

    conversation = Conversation.objects.filter(application=application).first()
    messages_list = []
    if conversation:
        mark_conversation_read(conversation, user)
        messages_list = list(conversation.messages.select_related('sender').order_by('created_at'))

    is_employer_side = user.is_company or user.is_platform_admin
    can_send = conversation is None or conversation.is_open
    can_close = bool(
        conversation
        and conversation.is_open
        and is_employer_side
    )
    can_reopen = bool(conversation and conversation.is_closed and is_employer_side)
    unread_count = 0
    if conversation:
        unread_count = conversation.messages.filter(read_at__isnull=True).exclude(sender=user).count()

    return {
        'conversation': conversation,
        'messages_list': messages_list,
        'can_send': can_send,
        'can_close': can_close,
        'can_reopen': can_reopen,
        'unread_count': unread_count,
    }
