from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

import json

from fastapi import HTTPException
from sqlalchemy.exc import SQLAlchemyError

from core.auth.models import CurrentUser
from core.db.base_service import BaseService
from core.logging import get_logger
from models.inbox_messages import InboxMessage
from models.schedule_subscriptions import (
    ScheduleSubscription,
    SubscriptionStatus,
)
from v1.inbox_messages.repository import InboxMessageRepository
from v1.inbox_messages.schemas import (
    InboxMessageAcceptRequest,
    InboxMessageListRequest,
    InboxMessageResponse,
    InboxMessageStatus,
    InboxMessageType,
    PermissionBits,
)

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession

logger = get_logger("v1.inbox_messages.service")


class InboxMessageService(BaseService):
    _repository: InboxMessageRepository
    _session: AsyncSession

    def __init__(
        self,
        repository: InboxMessageRepository,
        session: AsyncSession,
        current_user: CurrentUser | None,
    ) -> None:
        super().__init__(current_user=current_user)
        self._repository = repository
        self._session = session

    async def list_messages(
        self, request: InboxMessageListRequest
    ) -> list[InboxMessageResponse]:
        user_id = self.require_user_id()

        try:
            status = request.status.value if request.status else None
            messages = await self._repository.list_by_recipient(user_id, status)
        except SQLAlchemyError:
            logger.exception("Failed to list inbox messages", user_id=str(user_id))
            raise HTTPException(
                status_code=503, detail="Inbox message store unavailable"
            )

        return [self._to_response(message) for message in messages]

    async def accept_invitation(
        self,
        message_id: UUID,
        request: InboxMessageAcceptRequest,
    ) -> InboxMessageResponse:
        user_id = self.require_user_id()

        try:
            message = await self._repository.get_by_id(message_id, user_id)
            if message is None:
                raise HTTPException(status_code=404, detail="Inbox message not found")
            if message.status.value != InboxMessageStatus.PENDING.value:
                raise HTTPException(
                    status_code=400, detail="Inbox message already handled"
                )
            if (
                message.message_type.value != InboxMessageType.CALENDAR.value
                or message.schedule_item_id is None
            ):
                raise HTTPException(
                    status_code=400, detail="Message is not a calendar invitation"
                )

            invited_permission = self._parse_invited_permission(message.content)
            requested_permission = PermissionBits.encode(
                request.permission_view,
                request.permission_edit,
                request.permission_invite,
            )
            final_permission = requested_permission & invited_permission
            if final_permission == 0:
                raise HTTPException(
                    status_code=400,
                    detail="No valid permissions requested (must be subset of invited permissions)",
                )

            subscription = ScheduleSubscription(
                item_id=message.schedule_item_id,
                subscriber_id=user_id,
                permission=final_permission,
                status=SubscriptionStatus.ACTIVE,
                created_by=user_id,
            )
            self._session.add(subscription)
            updated = await self._repository.update_status(
                message_id,
                user_id,
                InboxMessageStatus.ACCEPTED.value,
            )
            if updated is None:
                await self._session.rollback()
                raise HTTPException(status_code=404, detail="Inbox message not found")
            await self._session.commit()
        except HTTPException:
            raise
        except SQLAlchemyError:
            await self._session.rollback()
            logger.exception(
                "Failed to accept inbox invitation",
                message_id=str(message_id),
                user_id=str(user_id),
            )
            raise HTTPException(
                status_code=503, detail="Inbox message store unavailable"
            )

        return self._to_response(updated)

    async def dismiss_invitation(self, message_id: UUID) -> InboxMessageResponse:
        user_id = self.require_user_id()

        try:
            message = await self._repository.get_by_id(message_id, user_id)
            if message is None:
                raise HTTPException(status_code=404, detail="Inbox message not found")
            if message.status.value != InboxMessageStatus.PENDING.value:
                raise HTTPException(
                    status_code=400, detail="Inbox message already handled"
                )

            updated = await self._repository.update_status(
                message_id,
                user_id,
                InboxMessageStatus.DISMISSED.value,
            )
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            logger.exception(
                "Failed to dismiss inbox invitation",
                message_id=str(message_id),
                user_id=str(user_id),
            )
            raise HTTPException(
                status_code=503, detail="Inbox message store unavailable"
            )

        if updated is None:
            raise HTTPException(status_code=404, detail="Inbox message not found")
        return self._to_response(updated)

    def _to_response(self, message: InboxMessage) -> InboxMessageResponse:
        return InboxMessageResponse(
            id=message.id,
            recipient_id=message.recipient_id,
            sender_id=message.sender_id,
            message_type=InboxMessageType(message.message_type),
            schedule_item_id=message.schedule_item_id,
            content=message.content,
            is_read=bool(message.is_read),
            status=InboxMessageStatus(message.status),
            created_at=message.created_at,
        )

    def _parse_invited_permission(self, content: str | None) -> int:
        if not content:
            return 0
        try:
            data = json.loads(content)
            return int(data.get("permission", 0))
        except (json.JSONDecodeError, ValueError, TypeError):
            return 0
