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_
import re

from app.core.database import get_db
from app.models.category import TreeCategory
from app.models.tree import Tree
from app.models.user import User
from app.schemas.category import CategoryCreate, CategoryUpdate, CategoryResponse, CategoryListResponse
from app.api.deps import get_current_active_user
from app.core.permissions import can_manage_category, can_create_category

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


def slugify(name: str) -> str:
    """Convert a name to a URL-safe slug."""
    slug = re.sub(r'[^a-zA-Z0-9 ]', '', name.lower())
    slug = re.sub(r' +', '-', slug.strip())
    return slug


@router.get("", response_model=list[CategoryListResponse])
async def list_categories(
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)],
    include_inactive: bool = Query(False, description="Include inactive categories"),
    account_only: bool = Query(False, description="Only show account-specific categories")
):
    """List categories visible to the user.

    Returns global categories plus account-specific categories for the user's account.
    """
    # Build query for accessible categories
    query = select(TreeCategory)

    # Filter by active status
    if not include_inactive:
        query = query.where(TreeCategory.is_active == True)

    # Filter by visibility: global OR user's account
    if account_only and current_user.account_id:
        query = query.where(TreeCategory.account_id == current_user.account_id)
    elif current_user.account_id:
        query = query.where(
            or_(
                TreeCategory.account_id.is_(None),  # Global
                TreeCategory.account_id == current_user.account_id  # User's account
            )
        )
    else:
        # User has no account, only show global categories
        query = query.where(TreeCategory.account_id.is_(None))

    query = query.order_by(TreeCategory.display_order, TreeCategory.name)

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

    # Get tree counts for each category
    response = []
    for cat in categories:
        # Count trees in this category
        count_query = select(func.count(Tree.id)).where(
            Tree.category_id == cat.id,
            Tree.is_active == True
        )
        count_result = await db.execute(count_query)
        tree_count = count_result.scalar() or 0

        response.append(CategoryListResponse(
            id=cat.id,
            name=cat.name,
            slug=cat.slug,
            description=cat.description,
            account_id=cat.account_id,
            display_order=cat.display_order,
            is_active=cat.is_active,
            tree_count=tree_count
        ))

    return response


@router.get("/{category_id}", response_model=CategoryResponse)
async def get_category(
    category_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Get a specific category by ID."""
    result = await db.execute(select(TreeCategory).where(TreeCategory.id == category_id))
    category = result.scalar_one_or_none()

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

    # Check access: global categories visible to all, account categories only to account members
    if category.account_id and category.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 category"
        )

    # Get tree count
    count_query = select(func.count(Tree.id)).where(
        Tree.category_id == category.id,
        Tree.is_active == True
    )
    count_result = await db.execute(count_query)
    tree_count = count_result.scalar() or 0

    return CategoryResponse(
        id=category.id,
        name=category.name,
        slug=category.slug,
        description=category.description,
        account_id=category.account_id,
        display_order=category.display_order,
        is_active=category.is_active,
        created_at=category.created_at,
        updated_at=category.updated_at,
        tree_count=tree_count
    )


@router.post("", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED)
async def create_category(
    category_data: CategoryCreate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Create a new category.

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

    # Generate slug
    slug = slugify(category_data.name)

    # Check for duplicate slug within same scope (global or account)
    existing_query = select(TreeCategory).where(
        TreeCategory.slug == slug,
        TreeCategory.account_id == category_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 category with slug '{slug}' already exists"
        )

    # Get next display order
    order_query = select(func.max(TreeCategory.display_order)).where(
        TreeCategory.account_id == category_data.account_id
    )
    order_result = await db.execute(order_query)
    max_order = order_result.scalar() or 0

    new_category = TreeCategory(
        name=category_data.name,
        slug=slug,
        description=category_data.description,
        account_id=category_data.account_id,
        display_order=max_order + 1,
        created_by=current_user.id
    )
    db.add(new_category)
    await db.commit()
    await db.refresh(new_category)

    return CategoryResponse(
        id=new_category.id,
        name=new_category.name,
        slug=new_category.slug,
        description=new_category.description,
        account_id=new_category.account_id,
        display_order=new_category.display_order,
        is_active=new_category.is_active,
        created_at=new_category.created_at,
        updated_at=new_category.updated_at,
        tree_count=0
    )


@router.put("/{category_id}", response_model=CategoryResponse)
async def update_category(
    category_id: UUID,
    category_data: CategoryUpdate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Update a category."""
    result = await db.execute(select(TreeCategory).where(TreeCategory.id == category_id))
    category = result.scalar_one_or_none()

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

    if not can_manage_category(current_user, category):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to update this category"
        )

    # Update fields
    update_data = category_data.model_dump(exclude_unset=True)

    # If name is being updated, regenerate slug
    if "name" in update_data:
        new_slug = slugify(update_data["name"])
        # Check for duplicate slug
        existing_query = select(TreeCategory).where(
            TreeCategory.slug == new_slug,
            TreeCategory.account_id == category.account_id,
            TreeCategory.id != category_id
        )
        existing = await db.execute(existing_query)
        if existing.scalar_one_or_none():
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"A category with slug '{new_slug}' already exists"
            )
        category.slug = new_slug

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

    await db.commit()
    await db.refresh(category)

    # Get tree count
    count_query = select(func.count(Tree.id)).where(
        Tree.category_id == category.id,
        Tree.is_active == True
    )
    count_result = await db.execute(count_query)
    tree_count = count_result.scalar() or 0

    return CategoryResponse(
        id=category.id,
        name=category.name,
        slug=category.slug,
        description=category.description,
        account_id=category.account_id,
        display_order=category.display_order,
        is_active=category.is_active,
        created_at=category.created_at,
        updated_at=category.updated_at,
        tree_count=tree_count
    )


@router.delete("/{category_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_category(
    category_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Soft delete (archive) a category."""
    result = await db.execute(select(TreeCategory).where(TreeCategory.id == category_id))
    category = result.scalar_one_or_none()

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

    if not can_manage_category(current_user, category):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to delete this category"
        )

    category.is_active = False
    await db.commit()
    return None
