from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock
from uuid import UUID, uuid4

import pytest
from fastapi import HTTPException

from core.auth.models import CurrentUser
from models.inbox_messages import (
    InboxMessage,
    InboxMessageStatus as InboxMessageModelStatus,
    InboxMessageType as InboxMessageModelType,
)
from models.schedule_subscriptions import ScheduleSubscription, SubscriptionStatus
from v1.inbox_messages.schemas import InboxMessageAcceptRequest, InboxMessageListRequest
from v1.inbox_messages.service import InboxMessageService


def _build_message(
    *,
    message_id: UUID,
    recipient_id: UUID,
    status: InboxMessageModelStatus = InboxMessageModelStatus.PENDING,
    message_type: InboxMessageModelType = InboxMessageModelType.CALENDAR,
    schedule_item_id: UUID | None = None,
    content: str = '{"permission": 7}',
) -> InboxMessage:
    message = MagicMock(spec=InboxMessage)
    message.id = message_id
    message.recipient_id = recipient_id
    message.sender_id = uuid4()
    message.message_type = message_type
    message.schedule_item_id = schedule_item_id
    message.content = content
    message.is_read = False
    message.status = status
    message.created_at = datetime(2026, 2, 28, 10, 0, 0, tzinfo=timezone.utc)
    return message


@pytest.mark.asyncio
async def test_list_messages_returns_messages() -> None:
    user_id = uuid4()
    repo = AsyncMock()
    repo.list_by_recipient.return_value = [
        _build_message(
            message_id=uuid4(),
            recipient_id=user_id,
            schedule_item_id=uuid4(),
        )
    ]
    session = AsyncMock()
    service = InboxMessageService(
        repository=repo,
        session=session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.list_messages(InboxMessageListRequest())

    assert len(result) == 1
    assert result[0].recipient_id == user_id
    assert result[0].status.value == "pending"
    repo.list_by_recipient.assert_awaited_once_with(user_id, None)


@pytest.mark.asyncio
async def test_accept_invitation_creates_subscription() -> None:
    user_id = uuid4()
    message_id = uuid4()
    item_id = uuid4()
    pending_message = _build_message(
        message_id=message_id,
        recipient_id=user_id,
        schedule_item_id=item_id,
    )
    accepted_message = _build_message(
        message_id=message_id,
        recipient_id=user_id,
        status=InboxMessageModelStatus.ACCEPTED,
        schedule_item_id=item_id,
    )

    repo = AsyncMock()
    repo.get_by_id.return_value = pending_message
    repo.update_status.return_value = accepted_message

    session = AsyncMock()
    session.add = MagicMock()

    service = InboxMessageService(
        repository=repo,
        session=session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.accept_invitation(
        message_id,
        InboxMessageAcceptRequest(
            permission_view=True,
            permission_edit=True,
            permission_invite=False,
        ),
    )

    session.add.assert_called_once()
    subscription = session.add.call_args.args[0]
    assert isinstance(subscription, ScheduleSubscription)
    assert subscription.item_id == item_id
    assert subscription.subscriber_id == user_id
    assert subscription.permission == 5  # view(1) + edit(4) = 5
    assert subscription.status == SubscriptionStatus.ACTIVE
    repo.update_status.assert_awaited_once_with(message_id, user_id, "accepted")
    session.commit.assert_awaited_once()
    assert result.status.value == "accepted"


@pytest.mark.asyncio
async def test_dismiss_invitation_updates_status() -> None:
    user_id = uuid4()
    message_id = uuid4()
    pending_message = _build_message(
        message_id=message_id,
        recipient_id=user_id,
        schedule_item_id=uuid4(),
    )
    dismissed_message = _build_message(
        message_id=message_id,
        recipient_id=user_id,
        status=InboxMessageModelStatus.DISMISSED,
        schedule_item_id=uuid4(),
    )

    repo = AsyncMock()
    repo.get_by_id.return_value = pending_message
    repo.update_status.return_value = dismissed_message

    session = AsyncMock()
    service = InboxMessageService(
        repository=repo,
        session=session,
        current_user=CurrentUser(id=user_id),
    )

    result = await service.dismiss_invitation(message_id)

    repo.update_status.assert_awaited_once_with(message_id, user_id, "dismissed")
    session.commit.assert_awaited_once()
    assert result.status.value == "dismissed"


@pytest.mark.asyncio
async def test_accept_noncalendar_message_fails() -> None:
    user_id = uuid4()
    message_id = uuid4()
    non_calendar_message = _build_message(
        message_id=message_id,
        recipient_id=user_id,
        message_type=InboxMessageModelType.FRIEND_REQUEST,
        schedule_item_id=None,
    )

    repo = AsyncMock()
    repo.get_by_id.return_value = non_calendar_message

    session = AsyncMock()
    session.add = MagicMock()

    service = InboxMessageService(
        repository=repo,
        session=session,
        current_user=CurrentUser(id=user_id),
    )

    with pytest.raises(HTTPException) as exc_info:
        await service.accept_invitation(message_id, InboxMessageAcceptRequest())

    assert exc_info.value.status_code == 400
    assert exc_info.value.detail == "Message is not a calendar invitation"
    session.add.assert_not_called()
    session.commit.assert_not_awaited()
