from typing import Annotated, Optional
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, or_, delete
from sqlalchemy.orm import selectinload

from app.core.database import get_db
from app.models.tag import TreeTag, tree_tag_assignments
from app.models.tree import Tree
from app.models.user import User
from app.schemas.tag import TagCreate, TagResponse, TagListResponse, TagAssignment
from app.api.deps import get_current_active_user
from app.core.permissions import can_manage_tree_tags, can_create_tag

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


@router.get("", response_model=list[TagListResponse])
async def list_tags(
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)],
    include_account: bool = Query(True, description="Include account-specific tags")
):
    """List tags visible to the user.

    Returns global tags plus account-specific tags for the user's account.
    Tags are ordered by usage count (most used first).
    """
    query = select(TreeTag)

    # Filter by visibility: global OR user's account
    if include_account and current_user.account_id:
        query = query.where(
            or_(
                TreeTag.account_id.is_(None),  # Global
                TreeTag.account_id == current_user.account_id  # User's account
            )
        )
    else:
        # Only show global tags
        query = query.where(TreeTag.account_id.is_(None))

    query = query.order_by(TreeTag.usage_count.desc(), TreeTag.name)

    result = await db.execute(query)
    tags = result.scalars().all()

    return [TagListResponse.model_validate(tag) for tag in tags]


@router.get("/search", response_model=list[TagListResponse])
async def search_tags(
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)],
    q: str = Query(..., min_length=1, description="Search query"),
    limit: int = Query(10, ge=1, le=50),
    include_account: bool = Query(True, description="Include account-specific tags")
):
    """Search/autocomplete tags.

    Searches tag names for the query string.
    Returns matching tags ordered by usage count.
    """
    escaped_q = q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
    query = select(TreeTag).where(
        TreeTag.name.ilike(f"%{escaped_q}%", escape="\\")
    )

    # Filter by visibility
    if include_account and current_user.account_id:
        query = query.where(
            or_(
                TreeTag.account_id.is_(None),
                TreeTag.account_id == current_user.account_id
            )
        )
    else:
        query = query.where(TreeTag.account_id.is_(None))

    query = query.order_by(TreeTag.usage_count.desc(), TreeTag.name).limit(limit)

    result = await db.execute(query)
    tags = result.scalars().all()

    return [TagListResponse.model_validate(tag) for tag in tags]


@router.get("/{tag_id}", response_model=TagResponse)
async def get_tag(
    tag_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Get a specific tag by ID."""
    result = await db.execute(select(TreeTag).where(TreeTag.id == tag_id))
    tag = result.scalar_one_or_none()

    if not tag:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tag not found"
        )

    # Check access: global tags visible to all, account tags only to account members
    if tag.account_id and tag.account_id != current_user.account_id and not current_user.is_super_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have access to this tag"
        )

    return TagResponse.model_validate(tag)


@router.post("", response_model=TagResponse, status_code=status.HTTP_201_CREATED)
async def create_tag(
    tag_data: TagCreate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Create a new tag.

    - Global admins can create global tags (account_id=None)
    - Account members can create account-specific tags for their account
    """
    if not can_create_tag(current_user, tag_data.account_id):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to create this tag"
        )

    # Generate slug
    slug = TreeTag.slugify(tag_data.name)

    # Check for duplicate slug within same scope (global or account)
    existing_query = select(TreeTag).where(
        TreeTag.slug == slug,
        TreeTag.account_id == tag_data.account_id
    )
    existing = await db.execute(existing_query)
    if existing.scalar_one_or_none():
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"A tag with slug '{slug}' already exists"
        )

    new_tag = TreeTag(
        name=tag_data.name,
        slug=slug,
        account_id=tag_data.account_id,
        created_by=current_user.id
    )
    db.add(new_tag)
    await db.commit()
    await db.refresh(new_tag)

    return TagResponse.model_validate(new_tag)


@router.post("/trees/{tree_id}", response_model=list[TagListResponse])
async def add_tags_to_tree(
    tree_id: UUID,
    tag_data: TagAssignment,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Add tags to a tree.

    If a tag doesn't exist, it will be created as a team tag (or global for admins).
    Returns the updated list of tags on the tree.
    """
    # Get tree with tags
    result = await db.execute(
        select(Tree)
        .options(selectinload(Tree.tags))
        .where(Tree.id == tree_id)
    )
    tree = result.scalar_one_or_none()

    if not tree:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tree not found"
        )

    if not can_manage_tree_tags(current_user, tree):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to manage tags on this tree"
        )

    # Process each tag name
    existing_tag_slugs = {tag.slug for tag in tree.tags}

    for tag_name in tag_data.tags:
        slug = TreeTag.slugify(tag_name)

        # Skip if already assigned
        if slug in existing_tag_slugs:
            continue

        # Try to find existing tag
        # Determine scope: use tree's account, or global for admin-owned trees
        tag_account_id = tree.account_id or (current_user.account_id if not current_user.is_super_admin else None)

        tag_query = select(TreeTag).where(
            TreeTag.slug == slug,
            or_(
                TreeTag.account_id.is_(None),  # Global tag
                TreeTag.account_id == tag_account_id  # Account tag
            )
        )
        tag_result = await db.execute(tag_query)
        tag = tag_result.scalar_one_or_none()

        if not tag:
            # Create new tag - prefer account scope unless admin creating on public tree
            new_account_id = tag_account_id
            if not can_create_tag(current_user, new_account_id):
                # Fall back to user's account if they can't create in tree's scope
                new_account_id = current_user.account_id

            tag = TreeTag(
                name=tag_name,
                slug=slug,
                account_id=new_account_id,
                created_by=current_user.id
            )
            db.add(tag)
            await db.flush()  # Get the ID

        # Add tag to tree
        tree.tags.append(tag)
        tag.usage_count += 1
        existing_tag_slugs.add(slug)

    await db.commit()
    await db.refresh(tree)

    # Return updated tags
    return [TagListResponse.model_validate(tag) for tag in tree.tags]


@router.delete("/trees/{tree_id}/{tag_slug}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_tag_from_tree(
    tree_id: UUID,
    tag_slug: str,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Remove a tag from a tree."""
    # Get tree with tags
    result = await db.execute(
        select(Tree)
        .options(selectinload(Tree.tags))
        .where(Tree.id == tree_id)
    )
    tree = result.scalar_one_or_none()

    if not tree:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tree not found"
        )

    if not can_manage_tree_tags(current_user, tree):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to manage tags on this tree"
        )

    # Find the tag to remove
    tag_to_remove = None
    for tag in tree.tags:
        if tag.slug == tag_slug:
            tag_to_remove = tag
            break

    if not tag_to_remove:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tag not found on this tree"
        )

    # Remove the tag from tree
    tree.tags.remove(tag_to_remove)
    tag_to_remove.usage_count = max(0, tag_to_remove.usage_count - 1)

    await db.commit()
    return None


@router.put("/trees/{tree_id}", response_model=list[TagListResponse])
async def replace_tree_tags(
    tree_id: UUID,
    tag_data: TagAssignment,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Replace all tags on a tree.

    Removes all existing tags and assigns the new list.
    If a tag doesn't exist, it will be created.
    Returns the updated list of tags on the tree.
    """
    # Get tree with tags
    result = await db.execute(
        select(Tree)
        .options(selectinload(Tree.tags))
        .where(Tree.id == tree_id)
    )
    tree = result.scalar_one_or_none()

    if not tree:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tree not found"
        )

    if not can_manage_tree_tags(current_user, tree):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to manage tags on this tree"
        )

    # Decrement usage count for all existing tags
    for tag in tree.tags:
        tag.usage_count = max(0, tag.usage_count - 1)

    # Clear all existing tags
    tree.tags.clear()

    # Add new tags
    tag_account_id = tree.account_id or (current_user.account_id if not current_user.is_super_admin else None)

    for tag_name in tag_data.tags:
        slug = TreeTag.slugify(tag_name)

        # Try to find existing tag
        tag_query = select(TreeTag).where(
            TreeTag.slug == slug,
            or_(
                TreeTag.account_id.is_(None),
                TreeTag.account_id == tag_account_id
            )
        )
        tag_result = await db.execute(tag_query)
        tag = tag_result.scalar_one_or_none()

        if not tag:
            # Create new tag
            new_account_id = tag_account_id
            if not can_create_tag(current_user, new_account_id):
                new_account_id = current_user.account_id

            tag = TreeTag(
                name=tag_name,
                slug=slug,
                account_id=new_account_id,
                created_by=current_user.id
            )
            db.add(tag)
            await db.flush()

        # Add tag to tree
        if tag not in tree.tags:  # Avoid duplicates from input
            tree.tags.append(tag)
            tag.usage_count += 1

    await db.commit()
    await db.refresh(tree)

    return [TagListResponse.model_validate(tag) for tag in tree.tags]


@router.get("/trees/{tree_id}", response_model=list[TagListResponse])
async def get_tree_tags(
    tree_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Get all tags assigned to a tree."""
    # Get tree with tags
    result = await db.execute(
        select(Tree)
        .options(selectinload(Tree.tags))
        .where(Tree.id == tree_id)
    )
    tree = result.scalar_one_or_none()

    if not tree:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Tree not found"
        )

    # Check if user can view the tree
    if not tree.is_public:
        if tree.author_id != current_user.id:
            if tree.account_id != current_user.account_id:
                if not current_user.is_super_admin:
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail="You don't have access to this tree"
                    )

    return [TagListResponse.model_validate(tag) for tag in tree.tags]
