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

from datetime import datetime
from datetime import timezone

from sqlalchemy import desc
from sqlalchemy import func
from sqlalchemy.orm import Session
from starlette.responses import JSONResponse
from starlette.routing import Mount
from starlette.routing import Route

from fenliu.database import get_db
from fenliu.models import HashtagStream
from fenliu.models import Post
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.fediverse import FediverseClient


async def get_hashtag_streams(request) -> JSONResponse:
    """Get all hashtag streams."""
    db: Session = next(get_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"

    query = db.query(HashtagStream)

    if active_only:
        query = query.filter(HashtagStream.active)

    streams = query.order_by(HashtagStream.created_at.desc()).offset(skip).limit(limit).all()

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


async def get_hashtag_stream(request) -> JSONResponse:
    """Get a specific hashtag stream."""
    db: Session = next(get_db())

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

    stream = db.query(HashtagStream).filter(HashtagStream.id == stream_id).first()

    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) -> JSONResponse:
    """Create a new hashtag stream."""
    db: Session = next(get_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)
    existing = (
        db.query(HashtagStream)
        .filter(
            HashtagStream.hashtag.ilike(stream_data.hashtag),
            HashtagStream.instance == stream_data.instance,
        )
        .first()
    )

    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)

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


async def update_hashtag_stream(request) -> JSONResponse:
    """Update a hashtag stream."""
    db: Session = next(get_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)

    db_stream = db.query(HashtagStream).filter(HashtagStream.id == stream_id).first()

    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(timezone.utc)
    db.commit()
    db.refresh(db_stream)

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


async def delete_hashtag_stream(request) -> JSONResponse:
    """Delete a hashtag stream."""
    db: Session = next(get_db())

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

    db_stream = db.query(HashtagStream).filter(HashtagStream.id == stream_id).first()

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

    db.delete(db_stream)
    db.commit()

    return JSONResponse({}, status_code=204)


async def fetch_hashtag_stream(request) -> JSONResponse:
    """Fetch posts for a hashtag stream."""
    db: Session = next(get_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", 20))

    stream = db.query(HashtagStream).filter(HashtagStream.id == stream_id).first()

    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:
        # Fetch posts from Fediverse
        client = FediverseClient(instance=stream.instance)
        processed_posts = await client.fetch_and_process_hashtag(stream.hashtag, limit)

        # Save posts to database
        posts_saved = 0
        for post_data in processed_posts:
            # Check if post already exists
            existing_post = db.query(Post).filter(Post.post_id == post_data["post_id"]).first()

            if not existing_post:
                # Create new post
                db_post = Post(
                    post_id=post_data["post_id"],
                    content=post_data["content"],
                    author_username=post_data["author_username"],
                    author_display_name=post_data["author_display_name"],
                    author_url=post_data["author_url"],
                    instance=stream.instance,
                    hashtags=post_data["hashtags"],
                    boosts=post_data["boosts"],
                    likes=post_data["likes"],
                    replies=post_data["replies"],
                    url=post_data["url"],
                    created_at=post_data["created_at"],
                    stream_id=stream_id,
                )
                db.add(db_post)
                posts_saved += 1

        # Update stream's last check time
        stream.last_check = datetime.now(timezone.utc)
        db.commit()

        return JSONResponse(
            FetchResponse(
                stream_id=stream_id,
                hashtag=stream.hashtag,
                posts_fetched=len(processed_posts),
                posts_saved=posts_saved,
                success=True,
            ).model_dump(mode="json")
        )

    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) -> JSONResponse:
    """Get posts with filtering."""
    db: Session = next(get_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")
    order_by = request.query_params.get("order_by", "created_at")
    order_desc = request.query_params.get("order_desc", "true").lower() == "true"

    query = db.query(Post)

    # Apply filters
    query = _apply_post_filters(query, stream_id, processed, min_spam_score, max_spam_score)

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

    posts = query.offset(skip).limit(limit).all()

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


def _apply_post_filters(query, stream_id, processed, min_spam_score, max_spam_score):
    """Apply filters to post query."""
    if stream_id is not None:
        query = query.filter(Post.stream_id == int(stream_id))

    if processed is not None:
        query = query.filter(Post.processed == (processed.lower() == "true"))

    if min_spam_score is not None:
        query = query.filter(Post.spam_score >= int(min_spam_score))

    if max_spam_score is not None:
        query = query.filter(Post.spam_score <= int(max_spam_score))

    return query


def _apply_post_ordering(query, order_by, order_desc):
    """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:
        query = query.order_by(desc(order_field))
    else:
        query = query.order_by(order_field)

    return query


async def get_post(request) -> JSONResponse:
    """Get a specific post."""
    db: Session = next(get_db())

    try:
        post_id = int(request.path_params["post_id"])
    except (KeyError, ValueError):
        return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

    post = db.query(Post).filter(Post.id == post_id).first()

    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"))


async def update_post(request) -> JSONResponse:
    """Update a post."""
    db: Session = next(get_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)

    db_post = db.query(Post).filter(Post.id == post_id).first()

    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)
    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) -> JSONResponse:
    """Delete a post."""
    db: Session = next(get_db())

    try:
        post_id = int(request.path_params["post_id"])
    except (KeyError, ValueError):
        return JSONResponse({"detail": "Invalid post ID"}, status_code=400)

    db_post = db.query(Post).filter(Post.id == post_id).first()

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

    db.delete(db_post)
    db.commit()

    return JSONResponse({}, status_code=204)


async def get_stats(_request) -> JSONResponse:
    """Get application statistics."""
    db: Session = next(get_db())

    # Get total and active streams
    total_streams = db.query(HashtagStream).count()
    active_streams = db.query(HashtagStream).filter(HashtagStream.active).count()

    # Get post statistics
    total_posts = db.query(Post).count()
    processed_posts = db.query(Post).filter(Post.processed).count()

    # Calculate average spam score
    avg_spam_score = None
    if processed_posts > 0:
        avg_result = db.query(func.avg(Post.spam_score)).filter(Post.processed).first()
        avg_spam_score = float(avg_result[0]) if avg_result[0] is not None else None

    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")
    )


# 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("/posts", get_posts, methods=["GET"]),
    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("/stats", get_stats, methods=["GET"]),
]

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