from __future__ import annotations

from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any
from uuid import UUID

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.friendships import FriendshipStatus
from models.inbox_messages import InboxMessageStatus, InboxMessageType
from v1.friendships.repository import FriendshipRepository
from v1.friendships.schemas import (
    FriendRequestCreate,
    FriendRequestResponse,
    FriendResponse,
)
from v1.users.repository import UserRepository

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession
    from v1.friendships.schemas import UserBasicInfo


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


class FriendshipService(BaseService):
    """Friendship service handling friend requests and friends list.

    Responsibilities:
    - Authorization checks
    - Validation (not self, not already friends, not blocked)
    - Transaction boundary (commit/rollback)
    - Converting ORM models to response schemas
    """

    _repository: FriendshipRepository
    _user_repository: UserRepository
    _session: AsyncSession

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

    async def send_request(self, request: FriendRequestCreate) -> FriendRequestResponse:
        user_id = self.require_user_id()
        target_user_id = request.target_user_id

        if user_id == target_user_id:
            raise HTTPException(
                status_code=400, detail="Cannot send friend request to yourself"
            )

        existing = await self._repository.get_friendship_between_users(
            user_id, target_user_id
        )
        if existing:
            if existing.status == FriendshipStatus.ACCEPTED:
                raise HTTPException(
                    status_code=400, detail="Already friends with this user"
                )
            if existing.status == FriendshipStatus.BLOCKED:
                raise HTTPException(
                    status_code=400, detail="Cannot send friend request to blocked user"
                )

        try:
            friendship, inbox = await self._repository.create_request(
                user_id, target_user_id
            )
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        logger.info(
            "friend_request_sent",
            extra={"initiator_id": str(user_id), "target_id": str(target_user_id)},
        )

        sender = await self._user_repository.get_by_user_id(user_id)
        recipient = await self._user_repository.get_by_user_id(target_user_id)

        return FriendRequestResponse(
            id=friendship.id,
            sender=self._build_user_basic_info(sender),
            recipient=self._build_user_basic_info(recipient),
            content=inbox.content,
            status="pending",
            created_at=friendship.created_at,
        )

    async def accept_request(self, friendship_id: UUID) -> FriendRequestResponse:
        user_id = self.require_user_id()

        try:
            friendship = await self._repository.get_friendship_by_id(friendship_id)
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        if friendship is None:
            raise HTTPException(status_code=404, detail="Friend request not found")

        recipient_id = (
            friendship.user_low_id
            if friendship.initiator_id == friendship.user_high_id
            else friendship.user_high_id
        )

        if recipient_id != user_id:
            logger.warning(
                "friend_request_accept_unauthorized",
                extra={
                    "actor_id": str(user_id),
                    "friendship_id": str(friendship_id),
                },
            )
            raise HTTPException(
                status_code=403, detail="Not authorized to accept this request"
            )

        if friendship.status != FriendshipStatus.PENDING:
            raise HTTPException(status_code=400, detail="Friend request is not pending")

        inbox = await self._repository.get_pending_inbox_for_recipient(
            user_id, friendship_id
        )
        if inbox is None:
            raise HTTPException(status_code=404, detail="Inbox message not found")

        friendship.status = FriendshipStatus.ACCEPTED
        inbox.status = InboxMessageStatus.ACCEPTED

        try:
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        sender_id = friendship.initiator_id
        if sender_id is None:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        logger.info(
            "friend_request_accepted",
            extra={
                "actor_id": str(user_id),
                "friendship_id": str(friendship_id),
                "initiator_id": str(sender_id),
            },
        )

        sender = await self._user_repository.get_by_user_id(sender_id)
        recipient = await self._user_repository.get_by_user_id(user_id)

        return FriendRequestResponse(
            id=friendship.id,
            sender=self._build_user_basic_info(sender),
            recipient=self._build_user_basic_info(recipient),
            content=inbox.content,
            status="accepted",
            created_at=friendship.created_at,
        )

    async def decline_request(self, friendship_id: UUID) -> FriendRequestResponse:
        user_id = self.require_user_id()

        try:
            friendship = await self._repository.get_friendship_by_id(friendship_id)
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        if friendship is None:
            raise HTTPException(status_code=404, detail="Friend request not found")

        recipient_id = (
            friendship.user_low_id
            if friendship.initiator_id == friendship.user_high_id
            else friendship.user_high_id
        )

        if recipient_id != user_id:
            logger.warning(
                "friend_request_decline_unauthorized",
                extra={
                    "actor_id": str(user_id),
                    "friendship_id": str(friendship_id),
                },
            )
            raise HTTPException(
                status_code=403, detail="Not authorized to decline this request"
            )

        if friendship.status != FriendshipStatus.PENDING:
            raise HTTPException(status_code=400, detail="Friend request is not pending")

        inbox = await self._repository.get_pending_inbox_for_recipient(
            user_id, friendship_id
        )

        friendship.status = FriendshipStatus.DECLINED
        if inbox:
            inbox.status = InboxMessageStatus.REJECTED

        try:
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        sender_id = friendship.initiator_id
        if sender_id is None:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        logger.info(
            "friend_request_declined",
            extra={
                "actor_id": str(user_id),
                "friendship_id": str(friendship_id),
                "initiator_id": str(sender_id),
            },
        )

        sender = await self._user_repository.get_by_user_id(sender_id)
        recipient = await self._user_repository.get_by_user_id(user_id)

        return FriendRequestResponse(
            id=friendship.id,
            sender=self._build_user_basic_info(sender),
            recipient=self._build_user_basic_info(recipient),
            content=inbox.content if inbox else None,
            status="rejected",
            created_at=friendship.created_at,
        )

    async def cancel_request(self, friendship_id: UUID) -> FriendRequestResponse:
        user_id = self.require_user_id()

        try:
            friendship = await self._repository.get_friendship_by_id(friendship_id)
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        if friendship is None:
            raise HTTPException(status_code=404, detail="Friend request not found")

        if friendship.initiator_id != user_id:
            logger.warning(
                "friend_request_cancel_unauthorized",
                extra={
                    "actor_id": str(user_id),
                    "friendship_id": str(friendship_id),
                },
            )
            raise HTTPException(
                status_code=403, detail="Not authorized to cancel this request"
            )

        if friendship.status != FriendshipStatus.PENDING:
            raise HTTPException(status_code=400, detail="Friend request is not pending")

        inbox = await self._repository.get_pending_inbox_for_recipient(
            friendship.user_high_id, friendship_id
        )

        friendship.status = FriendshipStatus.CANCELED
        if inbox:
            inbox.status = InboxMessageStatus.DISMISSED

        try:
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        sender = await self._user_repository.get_by_user_id(user_id)
        recipient_id = friendship.user_high_id
        if recipient_id is None:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )
        recipient = await self._user_repository.get_by_user_id(recipient_id)

        logger.info(
            "friend_request_canceled",
            extra={
                "actor_id": str(user_id),
                "friendship_id": str(friendship_id),
                "target_id": str(recipient_id),
            },
        )

        return FriendRequestResponse(
            id=friendship.id,
            sender=self._build_user_basic_info(sender),
            recipient=self._build_user_basic_info(recipient),
            content=inbox.content if inbox else None,
            status="canceled",
            created_at=friendship.created_at,
        )

    async def get_inbox(self) -> list[FriendRequestResponse]:
        user_id = self.require_user_id()

        try:
            inbox_messages = await self._repository.get_inbox_messages_for_user(
                user_id, InboxMessageStatus.PENDING
            )
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        result: list[FriendRequestResponse] = []
        for inbox in inbox_messages:
            if inbox.message_type != InboxMessageType.FRIEND_REQUEST:
                continue

            friendship_id = inbox.friendship_id
            if friendship_id is None:
                continue

            friendship = await self._repository.get_friendship_by_id(friendship_id)
            if friendship is None or friendship.status != FriendshipStatus.PENDING:
                continue

            sender_id = inbox.sender_id
            if sender_id is None:
                continue

            sender = await self._user_repository.get_by_user_id(sender_id)
            recipient = await self._user_repository.get_by_user_id(user_id)

            result.append(
                FriendRequestResponse(
                    id=friendship.id,
                    sender=self._build_user_basic_info(sender),
                    recipient=self._build_user_basic_info(recipient),
                    content=inbox.content,
                    status="pending",
                    created_at=friendship.created_at,
                )
            )

        return result

    async def get_outgoing_requests(self) -> list[FriendRequestResponse]:
        user_id = self.require_user_id()

        try:
            outgoing = await self._repository.get_outgoing_requests(user_id)
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        result: list[FriendRequestResponse] = []
        for friendship in outgoing:
            recipient_id = (
                friendship.user_low_id
                if friendship.initiator_id == friendship.user_high_id
                else friendship.user_high_id
            )
            sender = await self._user_repository.get_by_user_id(user_id)
            recipient = await self._user_repository.get_by_user_id(recipient_id)

            result.append(
                FriendRequestResponse(
                    id=friendship.id,
                    sender=self._build_user_basic_info(sender),
                    recipient=self._build_user_basic_info(recipient),
                    content=None,
                    status="pending",
                    created_at=friendship.created_at,
                )
            )

        return result

    async def get_friends_list(self) -> list[FriendResponse]:
        user_id = self.require_user_id()

        try:
            friendships = await self._repository.get_friends_list(user_id)
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        result: list[FriendResponse] = []
        for friendship in friendships:
            friend_id = (
                friendship.user_high_id
                if friendship.user_low_id == user_id
                else friendship.user_low_id
            )
            friend = await self._user_repository.get_by_user_id(friend_id)

            result.append(
                FriendResponse(
                    id=friendship.id,
                    friend=self._build_user_basic_info(friend),
                    status="active",
                    created_at=friendship.created_at,
                    accepted_at=friendship.updated_at,
                )
            )

        return result

    async def remove_friend(self, friend_id: UUID) -> FriendResponse:
        user_id = self.require_user_id()

        try:
            friendship = await self._repository.get_friendship_between_users(
                user_id, friend_id
            )
        except SQLAlchemyError:
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        if friendship is None:
            raise HTTPException(status_code=404, detail="Friendship not found")

        if friendship.status != FriendshipStatus.ACCEPTED:
            raise HTTPException(
                status_code=400, detail="Can only remove accepted friendships"
            )

        friendship.deleted_at = datetime.now(timezone.utc)

        try:
            await self._session.commit()
        except SQLAlchemyError:
            await self._session.rollback()
            raise HTTPException(
                status_code=503, detail="Friendship service unavailable"
            )

        logger.info(
            "friend_removed",
            extra={
                "actor_id": str(user_id),
                "friendship_id": str(friendship.id),
                "removed_friend_id": str(friend_id),
            },
        )

        friend = await self._user_repository.get_by_user_id(friend_id)

        return FriendResponse(
            id=friendship.id,
            friend=self._build_user_basic_info(friend),
            status="active",
            created_at=friendship.created_at,
            accepted_at=friendship.updated_at,
        )

    def _build_user_basic_info(self, profile: Any) -> "UserBasicInfo":
        from v1.friendships.schemas import UserBasicInfo

        if profile is None:
            return UserBasicInfo(id="", username="")

        p = profile  # type: ignore[assignment]
        return UserBasicInfo(
            id=str(p.id),
            username=p.username,
            avatar_url=p.avatar_url if hasattr(p, "avatar_url") else None,
        )
