"""API router for REST endpoints."""

import logging
from datetime import UTC
from datetime import datetime
from typing import cast

from sqlalchemy import desc
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.sql import Select
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.responses import Response
from starlette.routing import Mount
from starlette.routing import Route

from fenliu.api.api_keys import api_key_routes
from fenliu.api.curated import curated_routes
from fenliu.api.reblog_controls import reblog_controls_routes
from fenliu.custom_types import ScoringResult
from fenliu.database import get_db
from fenliu.models import ErrorHistory
from fenliu.models import HashtagStream
from fenliu.models import Post
from fenliu.models import QueueStats
from fenliu.models import ReviewFeedback
from fenliu.schemas import FetchResponse
from fenliu.schemas import HashtagStreamCreate
from fenliu.schemas import HashtagStreamResponse
from fenliu.schemas import HashtagStreamUpdate
from fenliu.schemas import PostResponse
from fenliu.schemas import PostUpdate
from fenliu.schemas import StatsResponse
from fenliu.services.export_eligibility import get_auto_reject_setting
from fenliu.services.export_eligibility import reject_blocked_posts
from fenliu.services.fediverse import FediverseClient
from fenliu.services.scheduler import get_scheduler
from fenliu.services.spam_scoring import SpamScoringService

# Schedule management constants
MIN_FETCH_INTERVAL = 5
MAX_FETCH_INTERVAL = 1440


def _validate_stream_id(path_params: dict) -> tuple[int | None, JSONResponse | None]:
    """Validate and extract stream ID from path params.

    Returns:
        Tuple of (stream_id, error_response). error_response is None if valid.

    """
    try:
        stream_id = int(path_params["stream_id"])
        return stream_id, None
    except (KeyError, ValueError):
        return None, JSONResponse({"detail": "Invalid stream ID"}, status_code=400)


async def _parse_request_json(request: Request) -> tuple[dict | None, JSONResponse | None]:
    """Parse JSON from request.

    Returns:
        Tuple of (body_dict, error_response). error_response is None if valid.

    """
    try:
        body = await request.json()
        return body, None
    except Exception:
        return None, JSONResponse({"detail": "Invalid JSON"}, status_code=400)


def _validate_fetch_interval(interval: int) -> tuple[bool, JSONResponse | None]:
    """Validate fetch interval value.

    Returns:
        Tuple of (is_valid, error_response). error_response is None if valid.

    """
    if interval < MIN_FETCH_INTERVAL or interval > MAX_FETCH_INTERVAL:
        msg = f"fetch_interval_minutes must be between {MIN_FETCH_INTERVAL} and {MAX_FETCH_INTERVAL}"
        return False, JSONResponse({"detail": msg}, status_code=400)
    return True, None


def _apply_schedule_updates(stream: HashtagStream, body: dict | None) -> tuple[bool, JSONResponse | None]:
    """Apply schedule configuration updates to stream.

    Returns:
        Tuple of (success, error_response). error_response is None if successful.

    """
    if body is None:
        return False, JSONResponse({"detail": "Request body is missing"}, status_code=400)
    if "enable_scheduling" in body:
        stream.enable_scheduling = bool(body["enable_scheduling"])

    if "fetch_interval_minutes" in body:
        try:
            interval = int(body["fetch_interval_minutes"])
        except (ValueError, TypeError):
            return False, JSONResponse({"detail": "fetch_interval_minutes must be an integer"}, status_code=400)

        is_valid, error = _validate_fetch_interval(interval)
        if not is_valid:
            return False, error

        stream.fetch_interval_minutes = interval
        stream.next_scheduled_fetch = None

    return True, None


def _format_schedule_response(stream: HashtagStream) -> dict:
    """Format stream schedule as JSON response."""
    return {
        "stream_id": stream.id,
        "hashtag": stream.hashtag,
        "enable_scheduling": stream.enable_scheduling,
        "fetch_interval_minutes": stream.fetch_interval_minutes,
        "next_scheduled_fetch": stream.next_scheduled_fetch.isoformat() if stream.next_scheduled_fetch else None,
    }


async def get_hashtag_streams(request: Request) -> JSONResponse:
    """Get all hashtag streams."""
    with get_db() as db:
        skip = int(request.query_params.get("skip", 0))
        limit = int(request.query_params.get("limit", 100))
        active_only = request.query_params.get("active_only", "").lower() == "true"

        stmt = select(HashtagStream)

        if active_only:
            stmt = stmt.where(HashtagStream.active)

        stmt = stmt.order_by(HashtagStream.created_at.desc()).offset(skip).limit(limit)

        result = db.execute(stmt)
        streams = result.scalars().all()

        return JSONResponse(
            [HashtagStreamResponse.model_validate(stream).model_dump(mode="json") for stream in streams]
        )


async def get_hashtag_stream(request: Request) -> JSONResponse:
    """Get a specific hashtag stream."""
    with get_db() as db:
        try:
            stream_id = int(request.path_params["stream_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid stream ID"}, status_code=400)

        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        result = db.execute(stmt)
        stream: HashtagStream | None = result.scalar_one_or_none()

        if not stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        return JSONResponse(HashtagStreamResponse.model_validate(stream).model_dump(mode="json"))


async def create_hashtag_stream(request: Request) -> JSONResponse:
    """Create a new hashtag stream."""
    with get_db() as db:
        try:
            data = await request.json()
            stream_data = HashtagStreamCreate.model_validate(data)
        except Exception as e:
            return JSONResponse({"detail": f"Invalid request data: {e!s}"}, status_code=400)

        # Check if hashtag already exists (case-insensitive)
        stmt = select(HashtagStream).where(
            HashtagStream.hashtag.ilike(stream_data.hashtag),
            HashtagStream.instance == stream_data.instance,
        )
        result = db.execute(stmt)
        existing: HashtagStream | None = result.scalar_one_or_none()

        if existing:
            return JSONResponse(
                {"detail": f"Hashtag #{stream_data.hashtag} already monitored on {stream_data.instance}"},
                status_code=400,
            )

        db_stream = HashtagStream(
            hashtag=stream_data.hashtag.lower(),
            instance=stream_data.instance,
            active=stream_data.active,
        )

        db.add(db_stream)
        db.commit()
        db.refresh(db_stream)

        # Schedule the new stream immediately if active
        if db_stream.active:
            try:
                scheduler = await get_scheduler()
                await scheduler.schedule_stream(db_stream)
            except Exception as e:
                # Log but don't fail the stream creation if scheduling fails
                logging.getLogger(__name__).warning(f"Failed to schedule new stream {db_stream.id}: {e}")

        return JSONResponse(HashtagStreamResponse.model_validate(db_stream).model_dump(mode="json"), status_code=201)


async def update_hashtag_stream(request: Request) -> JSONResponse:
    """Update a hashtag stream."""
    with get_db() as db:
        try:
            stream_id = int(request.path_params["stream_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid stream ID"}, status_code=400)

        try:
            data = await request.json()
            stream_update = HashtagStreamUpdate.model_validate(data)
        except Exception as e:
            return JSONResponse({"detail": f"Invalid request data: {e!s}"}, status_code=400)

        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        result = db.execute(stmt)
        db_stream: HashtagStream | None = result.scalar_one_or_none()

        if not db_stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        update_data = stream_update.model_dump(exclude_unset=True)
        for field, value in update_data.items():
            setattr(db_stream, field, value)

        db_stream.updated_at = datetime.now(UTC)
        db.commit()
        db.refresh(db_stream)

        # Reschedule the stream if active status or settings changed
        try:
            scheduler = await get_scheduler()
            await scheduler.schedule_stream(db_stream)
        except Exception as e:
            # Log but don't fail the update if scheduling fails
            logging.getLogger(__name__).warning(f"Failed to reschedule stream {db_stream.id}: {e}")

        return JSONResponse(HashtagStreamResponse.model_validate(db_stream).model_dump(mode="json"))


async def delete_hashtag_stream(request: Request) -> Response:
    """Delete a hashtag stream."""
    with get_db() as db:
        try:
            stream_id = int(request.path_params["stream_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid stream ID"}, status_code=400)

        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        result = db.execute(stmt)
        db_stream: HashtagStream | None = result.scalar_one_or_none()

        if not db_stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        try:
            db.delete(db_stream)
            db.commit()
        except Exception as e:
            return JSONResponse({"detail": f"Failed to delete stream: {e!s}"}, status_code=500)

        return Response(status_code=204)


async def get_stream_schedule(request: Request) -> JSONResponse:
    """Get the schedule configuration for a stream."""
    with get_db() as db:
        try:
            stream_id = int(request.path_params["stream_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid stream ID"}, status_code=400)

        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        stream = db.execute(stmt).scalar_one_or_none()

        if not stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        return JSONResponse(
            {
                "stream_id": stream.id,
                "hashtag": stream.hashtag,
                "enable_scheduling": stream.enable_scheduling,
                "fetch_interval_minutes": stream.fetch_interval_minutes,
                "next_scheduled_fetch": stream.next_scheduled_fetch.isoformat()
                if stream.next_scheduled_fetch
                else None,
                "last_check": stream.last_check.isoformat() if stream.last_check else None,
            }
        )


async def update_stream_schedule(request: Request) -> JSONResponse:
    """Update the schedule configuration for a stream."""
    # Validate stream ID
    stream_id, error = _validate_stream_id(request.path_params)
    if error:
        return error

    # Parse request body
    body, error = await _parse_request_json(request)
    if error:
        return error

    with get_db() as db:
        # Fetch stream
        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        stream = db.execute(stmt).scalar_one_or_none()

        if not stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        # Apply updates
        success, error = _apply_schedule_updates(stream, body)
        if not success:
            return cast(JSONResponse, error)

        try:
            db.commit()

            # Update scheduler
            scheduler = await get_scheduler()
            await scheduler.schedule_stream(stream)

            return JSONResponse(_format_schedule_response(stream))

        except Exception as e:
            db.rollback()
            return JSONResponse({"detail": f"Failed to update schedule: {e!s}"}, status_code=500)


def _save_fetched_posts(
    db: Session,
    processed_posts: list[dict],
    stream_id: int,
    instance: str,
) -> int:
    """Persist new posts from a fetch operation, scoring each one.

    Args:
        db: Open database session.
        processed_posts: Post dicts returned by :class:`FediverseClient`.
        stream_id: ID of the parent :class:`HashtagStream`.
        instance: Fediverse instance hostname used for the fetch.

    Returns:
        Number of posts that were newly saved (duplicates skipped).

    """
    scoring_service = SpamScoringService()
    posts_saved = 0
    for post_data in processed_posts:
        existing = db.execute(select(Post).where(Post.post_id == post_data["post_id"])).scalar_one_or_none()
        if existing:
            continue
        db_post = Post(
            post_id=post_data.get("post_id", ""),
            content=post_data.get("content", ""),
            author_username=post_data.get("author_username", ""),
            author_display_name=post_data.get("author_display_name", ""),
            author_url=post_data.get("author_url", ""),
            author_is_bot=post_data.get("author_is_bot", False),
            instance=instance,
            hashtags=post_data.get("hashtags", []),
            media_attachments=post_data.get("media_attachments", []),
            boosts=post_data.get("boosts", 0),
            likes=post_data.get("likes", 0),
            replies=post_data.get("replies", 0),
            url=post_data.get("url", ""),
            created_at=post_data.get("created_at"),
            stream_id=stream_id,
        )
        score_result = scoring_service.calculate_spam_score(post_data)
        db_post.spam_score = score_result["final_score"]
        db.add(db_post)
        posts_saved += 1
    if posts_saved > 0:
        db.flush()
    return posts_saved


async def fetch_hashtag_stream(request: Request) -> JSONResponse:
    """Fetch posts for a hashtag stream."""
    with get_db() as db:
        try:
            stream_id = int(request.path_params["stream_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid stream ID"}, status_code=400)

        limit = int(request.query_params.get("limit", 50))

        stmt = select(HashtagStream).where(HashtagStream.id == stream_id)
        result = db.execute(stmt)
        stream: HashtagStream | None = result.scalar_one_or_none()

        if not stream:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} not found"}, status_code=404)

        if not stream.active:
            return JSONResponse({"detail": f"Hashtag stream {stream_id} is inactive"}, status_code=400)

        try:
            client = FediverseClient(instance=stream.instance)
            try:
                processed_posts = await client.fetch_and_process_hashtag(stream.hashtag, limit)
            finally:
                await client.close()

            posts_saved = _save_fetched_posts(db, processed_posts, stream_id, stream.instance)
            stream.last_check = datetime.now(UTC)

            auto_rejected = 0
            if posts_saved > 0 and get_auto_reject_setting(db):
                auto_rejected = reject_blocked_posts(db)

            db.commit()

            response_data = FetchResponse(
                stream_id=stream_id,
                hashtag=stream.hashtag,
                posts_fetched=len(processed_posts),
                posts_saved=posts_saved,
                success=True,
            ).model_dump(mode="json")
            if auto_rejected:
                response_data["auto_rejected"] = auto_rejected
            return JSONResponse(response_data)

        except Exception as e:
            db.rollback()
            return JSONResponse(
                FetchResponse(
                    stream_id=stream_id,
                    hashtag=stream.hashtag,
                    posts_fetched=0,
                    posts_saved=0,
                    success=False,
                    error_message=str(e),
                ).model_dump(mode="json"),
                status_code=500,
            )


async def get_posts(request: Request) -> JSONResponse:
    """Get posts with filtering."""
    with get_db() as db:
        skip = int(request.query_params.get("skip", 0))
        limit = int(request.query_params.get("limit", 50))
        stream_id = request.query_params.get("stream_id")
        processed = request.query_params.get("processed")
        min_spam_score = request.query_params.get("min_spam_score")
        max_spam_score = request.query_params.get("max_spam_score")
        reviewed = request.query_params.get("reviewed")
        approved = request.query_params.get("approved")
        exported = request.query_params.get("exported")
        order_by = request.query_params.get("order_by", "created_at")
        order_desc = request.query_params.get("order_desc", "true").lower() == "true"

        stmt: Select[tuple[Post]] = select(Post)

        # Apply filters
        filters = {
            "stream_id": stream_id,
            "processed": processed,
            "min_spam_score": min_spam_score,
            "max_spam_score": max_spam_score,
            "reviewed": reviewed,
            "approved": approved,
            "exported": exported,
        }
        stmt = _apply_post_filters(stmt, filters)

        # Apply ordering
        stmt = _apply_post_ordering(stmt, order_by, order_desc)

        stmt = stmt.offset(skip).limit(limit)
        result = db.execute(stmt)
        posts = result.scalars().all()

        return JSONResponse([PostResponse.model_validate(post).model_dump(mode="json") for post in posts])


def _apply_post_filters(
    stmt: Select[tuple[Post]],
    filters: dict[str, str | None],
) -> Select[tuple[Post]]:
    """Apply filters to post query."""
    stream_id = filters.get("stream_id")
    processed = filters.get("processed")
    min_spam_score = filters.get("min_spam_score")
    max_spam_score = filters.get("max_spam_score")
    reviewed = filters.get("reviewed")
    approved = filters.get("approved")
    exported = filters.get("exported")

    if stream_id is not None:
        stmt = stmt.where(Post.stream_id == int(stream_id))

    if processed is not None:
        stmt = stmt.where(Post.processed == (processed.lower() == "true"))

    if min_spam_score is not None:
        stmt = stmt.where(Post.spam_score >= int(min_spam_score))

    if max_spam_score is not None:
        stmt = stmt.where(Post.spam_score <= int(max_spam_score))

    if reviewed is not None:
        stmt = stmt.where(Post.reviewed == (reviewed.lower() == "true"))

    if approved is not None:
        stmt = stmt.where(Post.approved == (approved.lower() == "true"))

    if exported is not None:
        stmt = stmt.where(Post.exported == (exported.lower() == "true"))

    return stmt


def _apply_post_ordering(
    stmt: Select[tuple[Post]],
    order_by: str,
    order_desc: bool,
) -> Select[tuple[Post]]:
    """Apply ordering to post query."""
    order_field = None
    if order_by == "created_at":
        order_field = Post.created_at
    elif order_by == "fetched_at":
        order_field = Post.fetched_at
    elif order_by == "boosts":
        order_field = Post.boosts
    elif order_by == "likes":
        order_field = Post.likes
    elif order_by == "spam_score":
        order_field = Post.spam_score
    else:
        order_field = Post.created_at

    if order_desc:
        stmt = stmt.order_by(desc(order_field))
    else:
        stmt = stmt.order_by(order_field)

    return stmt


async def get_post(request: Request) -> JSONResponse:
    """Get a specific post."""
    with get_db() as db:
        try:
            post_id = int(request.path_params["post_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

        stmt = select(Post).where(Post.id == post_id)
        result = db.execute(stmt)
        post: Post | None = result.scalar_one_or_none()

        if not post:
            return JSONResponse({"detail": f"Post {post_id} not found"}, status_code=404)

        return JSONResponse(PostResponse.model_validate(post).model_dump(mode="json"))


def _handle_review_updates(
    db: Session,
    post_id: int,
    update_data: dict,
    db_post: Post,
) -> None:
    """Handle review-related field updates."""
    # Check for review-related fields
    review_fields = {"manual_spam_score", "approved", "reviewer_notes"}
    if any(field in update_data for field in review_fields):
        db_post.reviewed = True
        db_post.reviewed_at = datetime.now(UTC)

        # Queue transition: approved → pending, rejected → remove from pending
        if "approved" in update_data:
            if update_data["approved"] is True:
                db_post.queue_status = "pending"
            elif update_data["approved"] is False and db_post.queue_status == "pending":
                db_post.queue_status = None

        # Create review feedback record for significant review actions
        _create_review_feedback(db, post_id, update_data)


def _create_review_feedback(
    db: Session,
    post_id: int,
    update_data: dict,
) -> None:
    """Create a review feedback record for significant review actions."""
    if "approved" in update_data or "manual_spam_score" in update_data:
        # Determine decision type
        if "approved" in update_data:
            decision = "approved" if update_data["approved"] else "rejected"
        else:
            decision = "score_adjusted"

        review_feedback = ReviewFeedback(
            post_id=post_id,
            decision=decision,
            manual_score=update_data.get("manual_spam_score"),
            reviewer_notes=update_data.get("reviewer_notes"),
        )
        db.add(review_feedback)


def _handle_export_update(update_data: dict, db_post: Post) -> None:
    """Handle export field updates."""
    if "exported" in update_data:
        if update_data["exported"]:
            db_post.export_date = datetime.now(UTC)
        else:
            db_post.export_date = None


async def update_post(request: Request) -> JSONResponse:
    """Update a post."""
    with get_db() as db:
        try:
            post_id = int(request.path_params["post_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

        try:
            data = await request.json()
            post_update = PostUpdate.model_validate(data)
        except Exception as e:
            return JSONResponse({"detail": f"Invalid request data: {e!s}"}, status_code=400)

        stmt = select(Post).where(Post.id == post_id)
        result = db.execute(stmt)
        db_post: Post | None = result.scalar_one_or_none()

        if not db_post:
            return JSONResponse({"detail": f"Post {post_id} not found"}, status_code=404)

        update_data = post_update.model_dump(exclude_unset=True)

        # Handle review-related updates
        _handle_review_updates(db, post_id, update_data, db_post)

        # Handle export updates
        _handle_export_update(update_data, db_post)

        for field, value in update_data.items():
            setattr(db_post, field, value)

        db.commit()
        db.refresh(db_post)

        return JSONResponse(PostResponse.model_validate(db_post).model_dump(mode="json"))


async def delete_post(request: Request) -> Response:
    """Delete a post, recording stats if it's in a tracked queue state."""
    with get_db() as db:
        try:
            post_id = int(request.path_params["post_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

        stmt = select(Post).where(Post.id == post_id)
        result = db.execute(stmt)
        db_post: Post | None = result.scalar_one_or_none()

        if not db_post:
            return JSONResponse({"detail": f"Post {post_id} not found"}, status_code=404)

        # Record stats for tracked queue states before deletion
        if db_post.queue_status in ("delivered", "error", "reserved"):
            stats = db.get(QueueStats, 1)
            if stats is None:
                stats = QueueStats(id=1)
                db.add(stats)
            if db_post.queue_status == "delivered":
                stats.total_deleted_delivered += 1
            elif db_post.queue_status == "error":
                # Create ErrorHistory record instead of counting
                db.add(ErrorHistory(error_reason=db_post.error_reason))
            elif db_post.queue_status == "reserved":
                stats.total_deleted_reserved += 1

        db.delete(db_post)
        db.commit()

        return Response(status_code=204)


async def get_stats(_request: Request) -> JSONResponse:
    """Get application statistics."""
    with get_db() as db:
        # Get total and active streams
        total_streams: int = db.scalar(select(func.count()).select_from(HashtagStream))
        active_streams: int = db.scalar(select(func.count()).select_from(HashtagStream).where(HashtagStream.active))

        # Get post statistics
        total_posts: int = db.scalar(select(func.count()).select_from(Post))
        processed_posts: int = db.scalar(select(func.count()).select_from(Post).where(Post.processed))

        # Calculate average spam score
        avg_spam_score: float | None = db.scalar(select(func.avg(Post.spam_score)).where(Post.processed))

        return JSONResponse(
            StatsResponse(
                total_streams=total_streams,
                active_streams=active_streams,
                total_posts=total_posts,
                processed_posts=processed_posts,
                average_spam_score=avg_spam_score,
            ).model_dump(mode="json")
        )


async def recalculate_post_score(request: Request) -> JSONResponse:
    """Recalculate and update spam score for a single post."""
    with get_db() as db:
        try:
            post_id: int = int(request.path_params["post_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

        stmt = select(Post).where(Post.id == post_id)
        result = db.execute(stmt)
        post: Post | None = result.scalar_one_or_none()

        if not post:
            return JSONResponse({"detail": f"Post {post_id} not found"}, status_code=404)

        try:
            scoring_service: SpamScoringService = SpamScoringService()
            score_result: ScoringResult = scoring_service.score_post(post)

            # Update post with new score
            post.spam_score = score_result["final_score"]
            db.commit()

            return JSONResponse(
                {
                    "post_id": post_id,
                    "spam_score": score_result["final_score"],
                    "triggered_rules": score_result["triggered_rules"],
                    "confidence": score_result["confidence"],
                    "details": score_result["rule_results"],
                }
            )
        except Exception as e:
            return JSONResponse({"detail": f"Error scoring post: {e!s}"}, status_code=500)


async def batch_score_posts(request: Request) -> JSONResponse:
    """Score multiple posts for spam."""
    with get_db() as db:
        try:
            data = await request.json()
            post_ids = data.get("post_ids", [])
            if not isinstance(post_ids, list):
                return JSONResponse({"detail": "post_ids must be a list"}, status_code=400)
        except Exception as e:
            return JSONResponse({"detail": f"Invalid request data: {e!s}"}, status_code=400)

        if not post_ids:
            return JSONResponse({"detail": "No post IDs provided"}, status_code=400)

        results = []
        scoring_service: SpamScoringService = SpamScoringService()

        for post_id in post_ids:
            try:
                stmt = select(Post).where(Post.id == post_id)
                result = db.execute(stmt)
                post: Post | None = result.scalar_one_or_none()
                if not post:
                    results.append(
                        {
                            "post_id": post_id,
                            "error": "Post not found",
                            "success": False,
                        }
                    )
                    continue

                score_result: ScoringResult = scoring_service.score_post(post)

                # Update post with new score
                post.spam_score = score_result["final_score"]

                results.append(
                    {
                        "post_id": post_id,
                        "spam_score": score_result["final_score"],
                        "triggered_rules": score_result["triggered_rules"],
                        "confidence": score_result["confidence"],
                        "success": True,
                    }
                )
            except Exception as e:
                results.append(
                    {
                        "post_id": post_id,
                        "error": str(e),
                        "success": False,
                    }
                )

        db.commit()

        return JSONResponse(
            {
                "results": results,
                "total_scored": len([r for r in results if r["success"]]),
                "total_failed": len([r for r in results if not r["success"]]),
            }
        )


async def get_score_analysis(request: Request) -> JSONResponse:
    """Get detailed scoring analysis for a post without updating the score."""
    with get_db() as db:
        try:
            post_id: int = int(request.path_params["post_id"])
        except (KeyError, ValueError):
            return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

        stmt = select(Post).where(Post.id == post_id)
        result = db.execute(stmt)
        post: Post | None = result.scalar_one_or_none()

        if not post:
            return JSONResponse({"detail": f"Post {post_id} not found"}, status_code=404)

        try:
            scoring_service: SpamScoringService = SpamScoringService()
            score_result: ScoringResult = scoring_service.score_post(post)

            return JSONResponse(
                {
                    "post_id": post_id,
                    "current_score": post.spam_score,
                    "calculated_score": score_result["final_score"],
                    "triggered_rules": score_result["triggered_rules"],
                    "confidence": score_result["confidence"],
                    "details": score_result["rule_results"],
                }
            )
        except Exception as e:
            return JSONResponse({"detail": f"Error analyzing post: {e!s}"}, status_code=500)


# Create API routes
api_routes = [
    Route("/hashtags", get_hashtag_streams, methods=["GET"]),
    Route("/hashtags", create_hashtag_stream, methods=["POST"]),
    Route("/hashtags/{stream_id}", get_hashtag_stream, methods=["GET"]),
    Route("/hashtags/{stream_id}", update_hashtag_stream, methods=["PATCH"]),
    Route("/hashtags/{stream_id}", delete_hashtag_stream, methods=["DELETE"]),
    Route("/hashtags/{stream_id}/fetch", fetch_hashtag_stream, methods=["POST"]),
    Route("/hashtags/{stream_id}/schedule", get_stream_schedule, methods=["GET"]),
    Route("/hashtags/{stream_id}/schedule", update_stream_schedule, methods=["PATCH"]),
    Route("/posts", get_posts, methods=["GET"]),
    Route("/posts/batch-score", batch_score_posts, methods=["POST"]),
    Route("/posts/{post_id}", get_post, methods=["GET"]),
    Route("/posts/{post_id}", update_post, methods=["PATCH"]),
    Route("/posts/{post_id}", delete_post, methods=["DELETE"]),
    Route("/posts/{post_id}/score", get_score_analysis, methods=["GET"]),
    Route("/posts/{post_id}/recalculate", recalculate_post_score, methods=["POST"]),
    Route("/stats", get_stats, methods=["GET"]),
    Mount("/api-keys", routes=api_key_routes),
    Mount("/reblog-controls", routes=reblog_controls_routes),
    Mount("/curated", routes=curated_routes),
]

# Create API router
api_router = Mount("/v1", routes=api_routes)
