"""Topics endpoint — two-level category hierarchy with aggregated counts."""

from __future__ import annotations

import logging
import os
from typing import Annotated, Any

import yaml
from fastapi import APIRouter, Depends, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from database import get_session
from models import Creator, TechniquePage
from schemas import (
    PaginatedResponse,
    TechniquePageRead,
    TopicCategory,
    TopicListResponse,
    TopicSubTopic,
)

logger = logging.getLogger("chrysopedia.topics")

router = APIRouter(prefix="/topics", tags=["topics"])

# Path to canonical_tags.yaml relative to the backend directory
_TAGS_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "config", "canonical_tags.yaml")


def _load_canonical_tags() -> list[dict[str, Any]]:
    """Load the canonical tag categories from YAML."""
    path = os.path.normpath(_TAGS_PATH)
    try:
        with open(path) as f:
            data = yaml.safe_load(f)
        return data.get("categories", [])
    except FileNotFoundError:
        logger.warning("canonical_tags.yaml not found at %s", path)
        return []


@router.get("", response_model=TopicListResponse)
async def list_topics(
    db: AsyncSession = Depends(get_session),
) -> TopicListResponse:
    """Return the two-level topic hierarchy with technique/creator counts per sub-topic.

    Categories come from ``canonical_tags.yaml``. Counts are computed
    from live DB data by matching ``topic_tags`` array contents.
    """
    categories = _load_canonical_tags()

    # Pre-fetch all technique pages with their tags and creator_ids for counting
    tp_stmt = select(
        TechniquePage.topic_category,
        TechniquePage.topic_tags,
        TechniquePage.creator_id,
    )
    tp_result = await db.execute(tp_stmt)
    tp_rows = tp_result.all()

    # Build per-sub-topic counts
    result: list[TopicCategory] = []
    for cat in categories:
        cat_name = cat.get("name", "")
        cat_desc = cat.get("description", "")
        sub_topic_names: list[str] = cat.get("sub_topics", [])

        sub_topics: list[TopicSubTopic] = []
        for st_name in sub_topic_names:
            technique_count = 0
            creator_ids: set[str] = set()

            for tp_cat, tp_tags, tp_creator_id in tp_rows:
                tags = tp_tags or []
                # Match if the sub-topic name appears in the technique's tags
                # or if the category matches and tag is in sub-topics
                if st_name.lower() in [t.lower() for t in tags]:
                    technique_count += 1
                    creator_ids.add(str(tp_creator_id))

            sub_topics.append(
                TopicSubTopic(
                    name=st_name,
                    technique_count=technique_count,
                    creator_count=len(creator_ids),
                )
            )

        result.append(
            TopicCategory(
                name=cat_name,
                description=cat_desc,
                sub_topics=sub_topics,
            )
        )

    return TopicListResponse(items=result, total=len(result))


@router.get("/{category_slug}/{subtopic_slug}", response_model=PaginatedResponse)
async def get_subtopic_techniques(
    category_slug: str,
    subtopic_slug: str,
    sort: Annotated[str, Query()] = "alpha",
    offset: Annotated[int, Query(ge=0)] = 0,
    limit: Annotated[int, Query(ge=1, le=100)] = 50,
    db: AsyncSession = Depends(get_session),
) -> PaginatedResponse:
    """Return technique pages filtered by sub-topic tag within a category.

    ``subtopic_slug`` is matched case-insensitively against elements of the
    ``topic_tags`` ARRAY column. ``category_slug`` is normalised and matched
    against ``topic_category`` to scope results to the correct category.
    Results are eager-loaded with the creator relation.
    """
    category_name = category_slug.replace("-", " ").title()
    subtopic_name = subtopic_slug.replace("-", " ")

    # Filter: category matches AND subtopic_name appears in topic_tags.
    # Tags are stored lowercase; slugs arrive lowercase. Use PG ARRAY contains (@>).
    stmt = (
        select(TechniquePage)
        .where(TechniquePage.topic_category.ilike(category_name))
        .where(TechniquePage.topic_tags.contains([subtopic_name]))
    )

    count_stmt = select(func.count()).select_from(stmt.subquery())
    count_result = await db.execute(count_stmt)
    total = count_result.scalar() or 0

    stmt = (
        stmt.options(selectinload(TechniquePage.creator))
    )

    # Apply sort ordering
    if sort == "newest":
        stmt = stmt.order_by(TechniquePage.created_at.desc())
    elif sort == "oldest":
        stmt = stmt.order_by(TechniquePage.created_at.asc())
    elif sort == "creator":
        stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id, isouter=True)
        stmt = stmt.order_by(Creator.name.asc(), TechniquePage.title.asc())
    else:
        # Default: "alpha" — alphabetical by title
        stmt = stmt.order_by(TechniquePage.title.asc())

    stmt = stmt.offset(offset).limit(limit)
    result = await db.execute(stmt)
    pages = result.scalars().all()

    items = []
    for p in pages:
        item = TechniquePageRead.model_validate(p)
        if p.creator:
            item.creator_name = p.creator.name
            item.creator_slug = p.creator.slug
        items.append(item)

    return PaginatedResponse(
        items=items,
        total=total,
        offset=offset,
        limit=limit,
    )


@router.get("/{category_slug}", response_model=PaginatedResponse)
async def get_topic_techniques(
    category_slug: str,
    sort: Annotated[str, Query()] = "alpha",
    offset: Annotated[int, Query(ge=0)] = 0,
    limit: Annotated[int, Query(ge=1, le=100)] = 50,
    db: AsyncSession = Depends(get_session),
) -> PaginatedResponse:
    """Return technique pages filtered by topic_category.

    The ``category_slug`` is matched case-insensitively against
    ``technique_pages.topic_category`` (e.g. 'sound-design' matches 'Sound design').
    """
    # Normalize slug to category name: replace hyphens with spaces, title-case
    category_name = category_slug.replace("-", " ").title()

    # Also try exact match on the slug form
    stmt = select(TechniquePage).where(
        TechniquePage.topic_category.ilike(category_name)
    )

    count_stmt = select(func.count()).select_from(stmt.subquery())
    count_result = await db.execute(count_stmt)
    total = count_result.scalar() or 0

    stmt = stmt.options(selectinload(TechniquePage.creator))

    # Apply sort ordering
    if sort == "newest":
        stmt = stmt.order_by(TechniquePage.created_at.desc())
    elif sort == "oldest":
        stmt = stmt.order_by(TechniquePage.created_at.asc())
    elif sort == "creator":
        stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id, isouter=True)
        stmt = stmt.order_by(Creator.name.asc(), TechniquePage.title.asc())
    else:
        # Default: "alpha" — alphabetical by title
        stmt = stmt.order_by(TechniquePage.title.asc())

    stmt = stmt.offset(offset).limit(limit)
    result = await db.execute(stmt)
    pages = result.scalars().all()

    items = []
    for p in pages:
        item = TechniquePageRead.model_validate(p)
        if p.creator:
            item.creator_name = p.creator.name
            item.creator_slug = p.creator.slug
        items.append(item)

    return PaginatedResponse(
        items=items,
        total=total,
        offset=offset,
        limit=limit,
    )
