"""Protocol classes defining each domain's service interface.

All cross-domain callers program against these Protocols.  Concrete
implementations (Sql*Service) and no-op stubs both satisfy them.
"""
from __future__ import annotations

from datetime import datetime
from typing import Protocol, runtime_checkable

from sqlalchemy.ext.asyncio import AsyncSession

from .dtos import (
    CalendarDTO,
    CalendarEntryDTO,
    TicketDTO,
    MarketPlaceDTO,
    ProductDTO,
    CartItemDTO,
    CartSummaryDTO,
    ActorProfileDTO,
    APActivityDTO,
    APFollowerDTO,
    RemoteActorDTO,
    RemotePostDTO,
    TimelineItemDTO,
    NotificationDTO,
)


@runtime_checkable
class CalendarService(Protocol):
    async def calendars_for_container(
        self, session: AsyncSession, container_type: str, container_id: int,
    ) -> list[CalendarDTO]: ...

    async def pending_entries(
        self, session: AsyncSession, *, user_id: int | None, session_id: str | None,
    ) -> list[CalendarEntryDTO]: ...

    async def entries_for_page(
        self, session: AsyncSession, page_id: int, *, user_id: int | None, session_id: str | None,
    ) -> list[CalendarEntryDTO]: ...

    async def entry_by_id(self, session: AsyncSession, entry_id: int) -> CalendarEntryDTO | None: ...

    async def associated_entries(
        self, session: AsyncSession, content_type: str, content_id: int, page: int,
    ) -> tuple[list[CalendarEntryDTO], bool]: ...

    async def toggle_entry_post(
        self, session: AsyncSession, entry_id: int, content_type: str, content_id: int,
    ) -> bool: ...

    async def adopt_entries_for_user(
        self, session: AsyncSession, user_id: int, session_id: str,
    ) -> None: ...

    async def claim_entries_for_order(
        self, session: AsyncSession, order_id: int, user_id: int | None,
        session_id: str | None, page_post_id: int | None,
    ) -> None: ...

    async def confirm_entries_for_order(
        self, session: AsyncSession, order_id: int, user_id: int | None,
        session_id: str | None,
    ) -> None: ...

    async def get_entries_for_order(
        self, session: AsyncSession, order_id: int,
    ) -> list[CalendarEntryDTO]: ...

    async def user_tickets(
        self, session: AsyncSession, *, user_id: int,
    ) -> list[TicketDTO]: ...

    async def user_bookings(
        self, session: AsyncSession, *, user_id: int,
    ) -> list[CalendarEntryDTO]: ...

    async def confirmed_entries_for_posts(
        self, session: AsyncSession, post_ids: list[int],
    ) -> dict[int, list[CalendarEntryDTO]]: ...

    async def pending_tickets(
        self, session: AsyncSession, *, user_id: int | None, session_id: str | None,
    ) -> list[TicketDTO]: ...

    async def tickets_for_page(
        self, session: AsyncSession, page_id: int, *, user_id: int | None, session_id: str | None,
    ) -> list[TicketDTO]: ...

    async def claim_tickets_for_order(
        self, session: AsyncSession, order_id: int, user_id: int | None,
        session_id: str | None, page_post_id: int | None,
    ) -> None: ...

    async def confirm_tickets_for_order(
        self, session: AsyncSession, order_id: int,
    ) -> None: ...

    async def get_tickets_for_order(
        self, session: AsyncSession, order_id: int,
    ) -> list[TicketDTO]: ...

    async def adopt_tickets_for_user(
        self, session: AsyncSession, user_id: int, session_id: str,
    ) -> None: ...

    async def adjust_ticket_quantity(
        self, session: AsyncSession, entry_id: int, count: int, *,
        user_id: int | None, session_id: str | None,
        ticket_type_id: int | None = None,
    ) -> int: ...

    async def entry_ids_for_content(
        self, session: AsyncSession, content_type: str, content_id: int,
    ) -> set[int]: ...

    async def upcoming_entries_for_container(
        self, session: AsyncSession,
        container_type: str | None = None, container_id: int | None = None,
        *, page: int = 1, per_page: int = 20,
    ) -> tuple[list[CalendarEntryDTO], bool]: ...

    async def visible_entries_for_period(
        self, session: AsyncSession, calendar_id: int,
        period_start: datetime, period_end: datetime,
        *, user_id: int | None, is_admin: bool, session_id: str | None,
    ) -> list[CalendarEntryDTO]: ...


@runtime_checkable
class MarketService(Protocol):
    async def marketplaces_for_container(
        self, session: AsyncSession, container_type: str, container_id: int,
    ) -> list[MarketPlaceDTO]: ...

    async def product_by_id(self, session: AsyncSession, product_id: int) -> ProductDTO | None: ...

    async def product_by_slug(self, session: AsyncSession, slug: str) -> ProductDTO | None: ...

    async def create_marketplace(
        self, session: AsyncSession, container_type: str, container_id: int,
        name: str, slug: str,
    ) -> MarketPlaceDTO: ...

    async def list_marketplaces(
        self, session: AsyncSession,
        container_type: str | None = None, container_id: int | None = None,
        *, page: int = 1, per_page: int = 20,
    ) -> tuple[list[MarketPlaceDTO], bool]: ...

    async def soft_delete_marketplace(
        self, session: AsyncSession, container_type: str, container_id: int,
        slug: str,
    ) -> bool: ...


@runtime_checkable
class CartService(Protocol):
    async def cart_summary(
        self, session: AsyncSession, *, user_id: int | None, session_id: str | None,
        page_slug: str | None = None,
    ) -> CartSummaryDTO: ...

    async def cart_items(
        self, session: AsyncSession, *, user_id: int | None, session_id: str | None,
    ) -> list[CartItemDTO]: ...

    async def adopt_cart_for_user(
        self, session: AsyncSession, user_id: int, session_id: str,
    ) -> None: ...


@runtime_checkable
class FederationService(Protocol):
    # -- Actor management -----------------------------------------------------
    async def get_actor_by_username(
        self, session: AsyncSession, username: str,
    ) -> ActorProfileDTO | None: ...

    async def get_actor_by_user_id(
        self, session: AsyncSession, user_id: int,
    ) -> ActorProfileDTO | None: ...

    async def create_actor(
        self, session: AsyncSession, user_id: int, preferred_username: str,
        display_name: str | None = None, summary: str | None = None,
    ) -> ActorProfileDTO: ...

    async def username_available(
        self, session: AsyncSession, username: str,
    ) -> bool: ...

    # -- Publishing (core cross-domain API) -----------------------------------
    async def publish_activity(
        self, session: AsyncSession, *,
        actor_user_id: int,
        activity_type: str,
        object_type: str,
        object_data: dict,
        source_type: str | None = None,
        source_id: int | None = None,
    ) -> APActivityDTO: ...

    # -- Queries --------------------------------------------------------------
    async def get_activity(
        self, session: AsyncSession, activity_id: str,
    ) -> APActivityDTO | None: ...

    async def get_outbox(
        self, session: AsyncSession, username: str,
        page: int = 1, per_page: int = 20,
        origin_app: str | None = None,
    ) -> tuple[list[APActivityDTO], int]: ...

    async def get_activity_for_source(
        self, session: AsyncSession, source_type: str, source_id: int,
    ) -> APActivityDTO | None: ...

    async def count_activities_for_source(
        self, session: AsyncSession, source_type: str, source_id: int,
        *, activity_type: str,
    ) -> int: ...

    # -- Followers ------------------------------------------------------------
    async def get_followers(
        self, session: AsyncSession, username: str,
        app_domain: str | None = None,
    ) -> list[APFollowerDTO]: ...

    async def get_followers_paginated(
        self, session: AsyncSession, username: str,
        page: int = 1, per_page: int = 20,
        app_domain: str | None = None,
    ) -> tuple[list[RemoteActorDTO], int]: ...

    async def add_follower(
        self, session: AsyncSession, username: str,
        follower_acct: str, follower_inbox: str, follower_actor_url: str,
        follower_public_key: str | None = None,
        app_domain: str = "federation",
    ) -> APFollowerDTO: ...

    async def remove_follower(
        self, session: AsyncSession, username: str, follower_acct: str,
        app_domain: str = "federation",
    ) -> bool: ...

    # -- Remote actors --------------------------------------------------------
    async def get_or_fetch_remote_actor(
        self, session: AsyncSession, actor_url: str,
    ) -> RemoteActorDTO | None: ...

    async def search_remote_actor(
        self, session: AsyncSession, acct: str,
    ) -> RemoteActorDTO | None: ...

    async def search_actors(
        self, session: AsyncSession, query: str, page: int = 1, limit: int = 20,
    ) -> tuple[list[RemoteActorDTO], int]: ...

    # -- Following (outbound) -------------------------------------------------
    async def send_follow(
        self, session: AsyncSession, local_username: str, remote_actor_url: str,
    ) -> None: ...

    async def get_following(
        self, session: AsyncSession, username: str,
        page: int = 1, per_page: int = 20,
    ) -> tuple[list[RemoteActorDTO], int]: ...

    async def accept_follow_response(
        self, session: AsyncSession, local_username: str, remote_actor_url: str,
    ) -> None: ...

    async def unfollow(
        self, session: AsyncSession, local_username: str, remote_actor_url: str,
    ) -> None: ...

    # -- Remote posts ---------------------------------------------------------
    async def ingest_remote_post(
        self, session: AsyncSession, remote_actor_id: int,
        activity_json: dict, object_json: dict,
    ) -> None: ...

    async def delete_remote_post(
        self, session: AsyncSession, object_id: str,
    ) -> None: ...

    async def get_remote_post(
        self, session: AsyncSession, object_id: str,
    ) -> RemotePostDTO | None: ...

    # -- Timelines ------------------------------------------------------------
    async def get_home_timeline(
        self, session: AsyncSession, actor_profile_id: int,
        before: datetime | None = None, limit: int = 20,
    ) -> list[TimelineItemDTO]: ...

    async def get_public_timeline(
        self, session: AsyncSession,
        before: datetime | None = None, limit: int = 20,
    ) -> list[TimelineItemDTO]: ...

    async def get_actor_timeline(
        self, session: AsyncSession, remote_actor_id: int,
        before: datetime | None = None, limit: int = 20,
    ) -> list[TimelineItemDTO]: ...

    # -- Local posts ----------------------------------------------------------
    async def create_local_post(
        self, session: AsyncSession, actor_profile_id: int,
        content: str, visibility: str = "public",
        in_reply_to: str | None = None,
    ) -> int: ...

    async def delete_local_post(
        self, session: AsyncSession, actor_profile_id: int, post_id: int,
    ) -> None: ...

    # -- Interactions ---------------------------------------------------------
    async def like_post(
        self, session: AsyncSession, actor_profile_id: int,
        object_id: str, author_inbox: str,
    ) -> None: ...

    async def unlike_post(
        self, session: AsyncSession, actor_profile_id: int,
        object_id: str, author_inbox: str,
    ) -> None: ...

    async def boost_post(
        self, session: AsyncSession, actor_profile_id: int,
        object_id: str, author_inbox: str,
    ) -> None: ...

    async def unboost_post(
        self, session: AsyncSession, actor_profile_id: int,
        object_id: str, author_inbox: str,
    ) -> None: ...

    # -- Notifications --------------------------------------------------------
    async def get_notifications(
        self, session: AsyncSession, actor_profile_id: int,
        before: datetime | None = None, limit: int = 20,
    ) -> list[NotificationDTO]: ...

    async def unread_notification_count(
        self, session: AsyncSession, actor_profile_id: int,
    ) -> int: ...

    async def mark_notifications_read(
        self, session: AsyncSession, actor_profile_id: int,
    ) -> None: ...

    # -- Stats ----------------------------------------------------------------
    async def get_stats(self, session: AsyncSession) -> dict: ...
