"""Per-app AP social blueprint: search, follow/unfollow, followers, following, actor timeline.

Lightweight social UI for blog/market/events. Federation keeps the full
social hub (timeline, compose, notifications, interactions).

All rendering uses s-expressions (no Jinja templates).
"""
from __future__ import annotations

import logging
from datetime import datetime

from quart import Blueprint, request, g, redirect, url_for, abort, Response

from shared.services.registry import services

log = logging.getLogger(__name__)


def create_ap_social_blueprint(app_name: str) -> Blueprint:
    """Create a per-app social blueprint scoped to *app_name*."""
    bp = Blueprint("ap_social", __name__, url_prefix="/social")

    # ------------------------------------------------------------------
    # Federation session — AP tables live in db_federation.
    # ------------------------------------------------------------------
    from shared.db.session import needs_federation_session, create_federation_session

    @bp.before_request
    async def _open_ap_session():
        if needs_federation_session():
            sess = create_federation_session()
            g._ap_s = sess
            g._ap_tx = await sess.begin()
            g._ap_own = True
        else:
            g._ap_s = g.s
            g._ap_own = False

    @bp.after_request
    async def _commit_ap_session(response):
        if getattr(g, "_ap_own", False):
            if 200 <= response.status_code < 400:
                try:
                    await g._ap_tx.commit()
                except Exception:
                    try:
                        await g._ap_tx.rollback()
                    except Exception:
                        pass
        return response

    @bp.teardown_request
    async def _close_ap_session(exc):
        if getattr(g, "_ap_own", False):
            s = getattr(g, "_ap_s", None)
            if s:
                if exc is not None or s.in_transaction():
                    tx = getattr(g, "_ap_tx", None)
                    if tx and tx.is_active:
                        try:
                            await tx.rollback()
                        except Exception:
                            pass
                try:
                    await s.close()
                except Exception:
                    pass

    @bp.before_request
    async def load_actor():
        if g.get("user"):
            actor = await services.federation.get_actor_by_user_id(g._ap_s, g.user.id)
            g._social_actor = actor

    def _require_actor():
        actor = getattr(g, "_social_actor", None)
        if not actor:
            abort(403, "You need to choose a federation username first")
        return actor

    async def _render_social_page(content: str, actor=None, title: str = "Social"):
        """Render a full social page or OOB response depending on request type."""
        from shared.browser.app.utils.htmx import is_htmx_request
        from shared.sx.page import get_template_context
        from shared.sx.helpers import full_page_sx, oob_page_sx, sx_response
        from shared.infrastructure.ap_social_sx import (
            _social_full_headers, _social_oob_headers,
        )

        tctx = await get_template_context()
        kw = {"actor": actor}

        if is_htmx_request():
            oob_headers = await _social_oob_headers(tctx, **kw)
            return sx_response(await oob_page_sx(
                oobs=oob_headers,
                content=content,
            ))
        else:
            header_rows = await _social_full_headers(tctx, **kw)
            return await full_page_sx(tctx, header_rows=header_rows, content=content)

    # -- Index ----------------------------------------------------------------

    @bp.get("/")
    async def index():
        actor = getattr(g, "_social_actor", None)
        from shared.infrastructure.ap_social_sx import social_index_content_sx
        content = social_index_content_sx(actor)
        return await _render_social_page(content, actor, title="Social")

    # -- Search ---------------------------------------------------------------

    @bp.get("/search")
    async def search():
        actor = getattr(g, "_social_actor", None)
        query = request.args.get("q", "").strip()
        actors = []
        total = 0
        followed_urls: set[str] = set()
        if query:
            actors, total = await services.federation.search_actors(g._ap_s, query)
            if actor:
                following, _ = await services.federation.get_following(
                    g._ap_s, actor.preferred_username, page=1, per_page=1000,
                )
                followed_urls = {a.actor_url for a in following}
        from shared.infrastructure.ap_social_sx import social_search_content_sx
        content = social_search_content_sx(query, actors, total, 1, followed_urls, actor)
        return await _render_social_page(content, actor, title="Search")

    @bp.get("/search/page")
    async def search_page():
        actor = getattr(g, "_social_actor", None)
        query = request.args.get("q", "").strip()
        page = request.args.get("page", 1, type=int)
        actors = []
        total = 0
        followed_urls: set[str] = set()
        if query:
            actors, total = await services.federation.search_actors(
                g._ap_s, query, page=page,
            )
            if actor:
                following, _ = await services.federation.get_following(
                    g._ap_s, actor.preferred_username, page=1, per_page=1000,
                )
                followed_urls = {a.actor_url for a in following}
        from shared.infrastructure.ap_social_sx import search_results_sx
        from shared.sx.helpers import sx_response
        content = search_results_sx(actors, total, page, query, followed_urls, actor)
        return sx_response(content)

    # -- Follow / Unfollow ----------------------------------------------------

    @bp.post("/follow")
    async def follow():
        actor = _require_actor()
        form = await request.form
        remote_actor_url = form.get("actor_url", "")
        if remote_actor_url:
            await services.federation.send_follow(
                g._ap_s, actor.preferred_username, remote_actor_url,
            )
        if request.headers.get("SX-Request") or request.headers.get("HX-Request"):
            return await _actor_card_response(actor, remote_actor_url, is_followed=True)
        return redirect(request.referrer or url_for("ap_social.search"))

    @bp.post("/unfollow")
    async def unfollow():
        actor = _require_actor()
        form = await request.form
        remote_actor_url = form.get("actor_url", "")
        if remote_actor_url:
            await services.federation.unfollow(
                g._ap_s, actor.preferred_username, remote_actor_url,
            )
        if request.headers.get("SX-Request") or request.headers.get("HX-Request"):
            return await _actor_card_response(actor, remote_actor_url, is_followed=False)
        return redirect(request.referrer or url_for("ap_social.search"))

    async def _actor_card_response(actor, remote_actor_url, is_followed):
        """Re-render a single actor card after follow/unfollow."""
        remote_dto = await services.federation.get_or_fetch_remote_actor(
            g._ap_s, remote_actor_url,
        )
        if not remote_dto:
            return Response("", status=200)
        followed_urls = {remote_actor_url} if is_followed else set()
        referer = request.referrer or ""
        if "/followers" in referer:
            list_type = "followers"
        else:
            list_type = "following"
        from shared.infrastructure.ap_social_sx import actor_list_items_sx
        from shared.sx.helpers import sx_response
        content = actor_list_items_sx(
            [remote_dto], 0, 1, list_type, followed_urls, actor,
        )
        return sx_response(content)

    # -- Followers ------------------------------------------------------------

    @bp.get("/followers")
    async def followers_list():
        actor = _require_actor()
        actors, total = await services.federation.get_followers_paginated(
            g._ap_s, actor.preferred_username, app_domain=app_name,
        )
        following, _ = await services.federation.get_following(
            g._ap_s, actor.preferred_username, page=1, per_page=1000,
        )
        followed_urls = {a.actor_url for a in following}
        from shared.infrastructure.ap_social_sx import social_followers_content_sx
        content = social_followers_content_sx(actors, total, 1, followed_urls, actor)
        return await _render_social_page(content, actor, title="Followers")

    @bp.get("/followers/page")
    async def followers_list_page():
        actor = _require_actor()
        page = request.args.get("page", 1, type=int)
        actors, total = await services.federation.get_followers_paginated(
            g._ap_s, actor.preferred_username, page=page, app_domain=app_name,
        )
        following, _ = await services.federation.get_following(
            g._ap_s, actor.preferred_username, page=1, per_page=1000,
        )
        followed_urls = {a.actor_url for a in following}
        from shared.infrastructure.ap_social_sx import actor_list_items_sx
        from shared.sx.helpers import sx_response
        content = actor_list_items_sx(actors, total, page, "followers", followed_urls, actor)
        return sx_response(content)

    # -- Following ------------------------------------------------------------

    @bp.get("/following")
    async def following_list():
        actor = _require_actor()
        actors, total = await services.federation.get_following(
            g._ap_s, actor.preferred_username,
        )
        from shared.infrastructure.ap_social_sx import social_following_content_sx
        content = social_following_content_sx(actors, total, 1, actor)
        return await _render_social_page(content, actor, title="Following")

    @bp.get("/following/page")
    async def following_list_page():
        actor = _require_actor()
        page = request.args.get("page", 1, type=int)
        actors, total = await services.federation.get_following(
            g._ap_s, actor.preferred_username, page=page,
        )
        from shared.infrastructure.ap_social_sx import actor_list_items_sx
        from shared.sx.helpers import sx_response
        content = actor_list_items_sx(actors, total, page, "following", set(), actor)
        return sx_response(content)

    # -- Actor timeline -------------------------------------------------------

    @bp.get("/actor/<int:id>")
    async def actor_timeline(id: int):
        actor = getattr(g, "_social_actor", None)
        from shared.models.federation import RemoteActor
        from sqlalchemy import select as sa_select
        remote = (
            await g._ap_s.execute(
                sa_select(RemoteActor).where(RemoteActor.id == id)
            )
        ).scalar_one_or_none()
        if not remote:
            abort(404)
        from shared.services.federation_impl import _remote_actor_to_dto
        remote_dto = _remote_actor_to_dto(remote)
        items = await services.federation.get_actor_timeline(g._ap_s, id)
        is_following = False
        if actor:
            from shared.models.federation import APFollowing
            existing = (
                await g._ap_s.execute(
                    sa_select(APFollowing).where(
                        APFollowing.actor_profile_id == actor.id,
                        APFollowing.remote_actor_id == id,
                    )
                )
            ).scalar_one_or_none()
            is_following = existing is not None
        from shared.infrastructure.ap_social_sx import social_actor_timeline_content_sx
        content = social_actor_timeline_content_sx(remote_dto, items, is_following, actor)
        return await _render_social_page(content, actor)

    @bp.get("/actor/<int:id>/timeline")
    async def actor_timeline_page(id: int):
        actor = getattr(g, "_social_actor", None)
        before_str = request.args.get("before")
        before = None
        if before_str:
            try:
                before = datetime.fromisoformat(before_str)
            except ValueError:
                pass
        items = await services.federation.get_actor_timeline(
            g._ap_s, id, before=before,
        )
        from shared.infrastructure.ap_social_sx import timeline_items_sx
        from shared.sx.helpers import sx_response
        content = timeline_items_sx(items, "actor", id, actor)
        return sx_response(content)

    return bp
