from uuid import UUID
from typing import Optional
from datetime import datetime, timezone
from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select, func, desc, Integer, case
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.core.database import get_db
from app.api.deps import get_current_active_user, require_engineer_or_admin
from app.core.permissions import can_view_step, can_edit_step
from app.core.filters import build_step_visibility_filter
from app.models.user import User
from app.models.step_library import StepLibrary, StepRating
from app.models.step_category import StepCategory
from app.schemas.step_library import (
    StepLibraryCreate,
    StepLibraryUpdate,
    StepLibraryResponse,
    StepLibraryListResponse,
    StepRatingCreate,
    StepRatingUpdate,
    StepRatingResponse,
    PopularTagResponse,
)

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


async def get_step_or_404(
    step_id: UUID,
    db: AsyncSession,
    current_user: User,
    check_view: bool = True,
    check_edit: bool = False
) -> StepLibrary:
    """Get step by ID with permission checks."""
    result = await db.execute(
        select(StepLibrary).where(
            StepLibrary.id == step_id,
            StepLibrary.is_active == True
        ).options(selectinload(StepLibrary.source_tree))
    )
    step = result.scalar_one_or_none()
    if not step:
        raise HTTPException(status_code=404, detail="Step not found")

    if check_view and not can_view_step(current_user, step):
        raise HTTPException(status_code=403, detail="Not authorized to view this step")

    if check_edit and not can_edit_step(current_user, step):
        raise HTTPException(status_code=403, detail="Not authorized to modify this step")

    return step



@router.get("", response_model=list[StepLibraryListResponse])
async def list_steps(
    visibility: Optional[str] = Query(None, pattern="^(private|team|public)$"),
    category_id: Optional[UUID] = None,
    tags: Optional[list[str]] = Query(None),
    min_rating: Optional[float] = Query(None, ge=0, le=5),
    step_type: Optional[str] = Query(None, pattern="^(decision|action|solution)$"),
    sort_by: str = Query("recent", pattern="^(recent|popular|highest_rated|most_used)$"),
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """List steps with filters and pagination."""
    query = select(StepLibrary).where(
        StepLibrary.is_active == True,
        build_step_visibility_filter(current_user)
    ).options(selectinload(StepLibrary.source_tree))

    # Apply filters
    if visibility:
        query = query.where(StepLibrary.visibility == visibility)
    if category_id:
        query = query.where(StepLibrary.category_id == category_id)
    if tags:
        # Match any of the provided tags
        query = query.where(StepLibrary.tags.overlap(tags))
    if min_rating is not None:
        query = query.where(StepLibrary.rating_average >= Decimal(str(min_rating)))
    if step_type:
        query = query.where(StepLibrary.step_type == step_type)

    # Apply sorting
    if sort_by == "recent":
        query = query.order_by(desc(StepLibrary.created_at))
    elif sort_by == "popular" or sort_by == "most_used":
        query = query.order_by(desc(StepLibrary.usage_count))
    elif sort_by == "highest_rated":
        query = query.order_by(desc(StepLibrary.rating_average), desc(StepLibrary.rating_count))

    # Apply pagination
    query = query.offset(offset).limit(limit)

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

    # Fetch category names and author names
    response = []
    for step in steps:
        step_dict = {
            "id": step.id,
            "title": step.title,
            "step_type": step.step_type,
            "visibility": step.visibility,
            "category_id": step.category_id,
            "tags": step.tags,
            "usage_count": step.usage_count,
            "rating_average": step.rating_average,
            "rating_count": step.rating_count,
            "is_featured": step.is_featured,
            "created_by": step.created_by,
            "created_at": step.created_at,
            "is_flow_synced": step.is_flow_synced,
            "source_tree_name": step.source_tree.name if step.source_tree else None,
        }

        # Get category name if exists
        if step.category_id:
            cat_result = await db.execute(
                select(StepCategory.name).where(StepCategory.id == step.category_id)
            )
            cat_name = cat_result.scalar_one_or_none()
            step_dict["category_name"] = cat_name

        # Get author name
        author_result = await db.execute(
            select(User.name).where(User.id == step.created_by)
        )
        author_name = author_result.scalar_one_or_none()
        step_dict["author_name"] = author_name

        response.append(StepLibraryListResponse(**step_dict))

    return response


@router.get("/search", response_model=list[StepLibraryListResponse])
async def search_steps(
    q: str = Query(..., min_length=1),
    limit: int = Query(20, ge=1, le=100),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Full-text search for steps."""
    # Use PostgreSQL full-text search
    search_query = func.to_tsquery('english', q.replace(' ', ' & '))

    query = select(StepLibrary).where(
        StepLibrary.is_active == True,
        build_step_visibility_filter(current_user),
        func.to_tsvector('english', StepLibrary.title).match(search_query)
    ).options(selectinload(StepLibrary.source_tree)).order_by(desc(StepLibrary.rating_average)).limit(limit)

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

    response = []
    for step in steps:
        step_dict = {
            "id": step.id,
            "title": step.title,
            "step_type": step.step_type,
            "visibility": step.visibility,
            "category_id": step.category_id,
            "tags": step.tags,
            "usage_count": step.usage_count,
            "rating_average": step.rating_average,
            "rating_count": step.rating_count,
            "is_featured": step.is_featured,
            "created_by": step.created_by,
            "created_at": step.created_at,
            "is_flow_synced": step.is_flow_synced,
            "source_tree_name": step.source_tree.name if step.source_tree else None,
        }

        if step.category_id:
            cat_result = await db.execute(
                select(StepCategory.name).where(StepCategory.id == step.category_id)
            )
            step_dict["category_name"] = cat_result.scalar_one_or_none()

        author_result = await db.execute(
            select(User.name).where(User.id == step.created_by)
        )
        step_dict["author_name"] = author_result.scalar_one_or_none()

        response.append(StepLibraryListResponse(**step_dict))

    return response


@router.get("/tags/popular", response_model=list[PopularTagResponse])
async def get_popular_tags(
    limit: int = Query(20, ge=1, le=50),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Get popular tags with usage counts."""
    # Use unnest to expand arrays and count occurrences
    query = select(
        func.unnest(StepLibrary.tags).label('tag'),
        func.count().label('count')
    ).where(
        StepLibrary.is_active == True,
        build_step_visibility_filter(current_user)
    ).group_by(
        func.unnest(StepLibrary.tags)
    ).order_by(
        desc(func.count())
    ).limit(limit)

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

    return [PopularTagResponse(tag=row.tag, count=row.count) for row in tags]


@router.get("/{step_id}", response_model=StepLibraryResponse)
async def get_step(
    step_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Get a step by ID."""
    step = await get_step_or_404(step_id, db, current_user, check_view=True)

    response_dict = {
        "id": step.id,
        "title": step.title,
        "step_type": step.step_type,
        "content": step.content,
        "category_id": step.category_id,
        "tags": step.tags,
        "visibility": step.visibility,
        "created_by": step.created_by,
        "account_id": step.account_id,
        "usage_count": step.usage_count,
        "rating_average": step.rating_average,
        "rating_count": step.rating_count,
        "helpful_yes": step.helpful_yes,
        "helpful_no": step.helpful_no,
        "is_featured": step.is_featured,
        "is_verified": step.is_verified,
        "is_active": step.is_active,
        "created_at": step.created_at,
        "updated_at": step.updated_at,
        "is_flow_synced": step.is_flow_synced,
        "source_tree_name": step.source_tree.name if step.source_tree else None,
    }

    # Get category name if exists
    if step.category_id:
        cat_result = await db.execute(
            select(StepCategory.name).where(StepCategory.id == step.category_id)
        )
        response_dict["category_name"] = cat_result.scalar_one_or_none()

    # Get author name
    author_result = await db.execute(
        select(User.name).where(User.id == step.created_by)
    )
    response_dict["author_name"] = author_result.scalar_one_or_none()

    return StepLibraryResponse(**response_dict)


@router.post("", response_model=StepLibraryResponse, status_code=201)
async def create_step(
    step_data: StepLibraryCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(require_engineer_or_admin)
):
    """Create a new step (engineers and admins only, not viewers)."""
    # Validate category if provided
    if step_data.category_id:
        cat_result = await db.execute(
            select(StepCategory).where(
                StepCategory.id == step_data.category_id,
                StepCategory.is_active == True
            )
        )
        if not cat_result.scalar_one_or_none():
            raise HTTPException(status_code=400, detail="Invalid category")

    # Account validation: can only set account_id to own account
    account_id = step_data.account_id
    if account_id and account_id != current_user.account_id and not current_user.is_super_admin:
        raise HTTPException(status_code=403, detail="Cannot create step for another account")

    step = StepLibrary(
        title=step_data.title,
        step_type=step_data.step_type,
        content=step_data.content.model_dump(),
        category_id=step_data.category_id,
        tags=step_data.tags,
        visibility=step_data.visibility,
        created_by=current_user.id,
        account_id=account_id or current_user.account_id,
    )

    db.add(step)
    await db.commit()
    await db.refresh(step)

    # Build response
    response_dict = {
        "id": step.id,
        "title": step.title,
        "step_type": step.step_type,
        "content": step.content,
        "category_id": step.category_id,
        "tags": step.tags,
        "visibility": step.visibility,
        "created_by": step.created_by,
        "account_id": step.account_id,
        "usage_count": step.usage_count,
        "rating_average": step.rating_average,
        "rating_count": step.rating_count,
        "helpful_yes": step.helpful_yes,
        "helpful_no": step.helpful_no,
        "is_featured": step.is_featured,
        "is_verified": step.is_verified,
        "is_active": step.is_active,
        "created_at": step.created_at,
        "updated_at": step.updated_at,
        "author_name": current_user.name,
    }

    if step.category_id:
        cat_result = await db.execute(
            select(StepCategory.name).where(StepCategory.id == step.category_id)
        )
        response_dict["category_name"] = cat_result.scalar_one_or_none()

    return StepLibraryResponse(**response_dict)


@router.put("/{step_id}", response_model=StepLibraryResponse)
async def update_step(
    step_id: UUID,
    step_data: StepLibraryUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Update a step (owner or admin only)."""
    step = await get_step_or_404(step_id, db, current_user, check_edit=True)

    if step.is_flow_synced:
        raise HTTPException(
            status_code=400,
            detail="Flow-synced steps are read-only. Fork to customize."
        )

    # Validate category if being updated
    if step_data.category_id:
        cat_result = await db.execute(
            select(StepCategory).where(
                StepCategory.id == step_data.category_id,
                StepCategory.is_active == True
            )
        )
        if not cat_result.scalar_one_or_none():
            raise HTTPException(status_code=400, detail="Invalid category")

    # Apply updates
    update_data = step_data.model_dump(exclude_unset=True)
    if 'content' in update_data and update_data['content']:
        update_data['content'] = update_data['content'].model_dump() if hasattr(update_data['content'], 'model_dump') else update_data['content']

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

    step.updated_at = datetime.now(timezone.utc)

    await db.commit()
    await db.refresh(step)

    # Build response
    response_dict = {
        "id": step.id,
        "title": step.title,
        "step_type": step.step_type,
        "content": step.content,
        "category_id": step.category_id,
        "tags": step.tags,
        "visibility": step.visibility,
        "created_by": step.created_by,
        "account_id": step.account_id,
        "usage_count": step.usage_count,
        "rating_average": step.rating_average,
        "rating_count": step.rating_count,
        "helpful_yes": step.helpful_yes,
        "helpful_no": step.helpful_no,
        "is_featured": step.is_featured,
        "is_verified": step.is_verified,
        "is_active": step.is_active,
        "created_at": step.created_at,
        "updated_at": step.updated_at,
    }

    if step.category_id:
        cat_result = await db.execute(
            select(StepCategory.name).where(StepCategory.id == step.category_id)
        )
        response_dict["category_name"] = cat_result.scalar_one_or_none()

    author_result = await db.execute(
        select(User.name).where(User.id == step.created_by)
    )
    response_dict["author_name"] = author_result.scalar_one_or_none()

    return StepLibraryResponse(**response_dict)


@router.delete("/{step_id}", status_code=204)
async def delete_step(
    step_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Soft delete a step (owner or admin only)."""
    step = await get_step_or_404(step_id, db, current_user, check_edit=True)

    step.is_active = False
    step.updated_at = datetime.now(timezone.utc)

    await db.commit()
    return None


# Rating endpoints
@router.post("/{step_id}/rate", response_model=StepRatingResponse, status_code=201)
async def rate_step(
    step_id: UUID,
    rating_data: StepRatingCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Rate a step (1-5 stars with optional review)."""
    step = await get_step_or_404(step_id, db, current_user, check_view=True)

    # Check if user already rated
    existing = await db.execute(
        select(StepRating).where(
            StepRating.step_id == step_id,
            StepRating.user_id == current_user.id
        )
    )
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="You have already rated this step. Use PUT to update.")

    rating = StepRating(
        step_id=step_id,
        user_id=current_user.id,
        rating=rating_data.rating,
        was_helpful=rating_data.was_helpful,
        review_text=rating_data.review_text,
        session_id=rating_data.session_id,
        is_verified_use=rating_data.session_id is not None,
    )

    db.add(rating)

    # Update aggregated ratings on step
    await _update_step_ratings(db, step)

    await db.commit()
    await db.refresh(rating)

    return StepRatingResponse(
        id=rating.id,
        step_id=rating.step_id,
        user_id=rating.user_id,
        rating=rating.rating,
        was_helpful=rating.was_helpful,
        review_text=rating.review_text,
        is_verified_use=rating.is_verified_use,
        session_id=rating.session_id,
        is_visible=rating.is_visible,
        created_at=rating.created_at,
        updated_at=rating.updated_at,
        user_name=current_user.name,
    )


@router.put("/{step_id}/rate", response_model=StepRatingResponse)
async def update_rating(
    step_id: UUID,
    rating_data: StepRatingUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Update your rating for a step."""
    step = await get_step_or_404(step_id, db, current_user, check_view=True)

    result = await db.execute(
        select(StepRating).where(
            StepRating.step_id == step_id,
            StepRating.user_id == current_user.id
        )
    )
    rating = result.scalar_one_or_none()
    if not rating:
        raise HTTPException(status_code=404, detail="Rating not found. Use POST to create.")

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

    rating.updated_at = datetime.now(timezone.utc)

    # Update aggregated ratings on step
    await _update_step_ratings(db, step)

    await db.commit()
    await db.refresh(rating)

    return StepRatingResponse(
        id=rating.id,
        step_id=rating.step_id,
        user_id=rating.user_id,
        rating=rating.rating,
        was_helpful=rating.was_helpful,
        review_text=rating.review_text,
        is_verified_use=rating.is_verified_use,
        session_id=rating.session_id,
        is_visible=rating.is_visible,
        created_at=rating.created_at,
        updated_at=rating.updated_at,
        user_name=current_user.name,
    )


@router.delete("/{step_id}/rate", status_code=204)
async def delete_rating(
    step_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Delete your rating for a step."""
    step = await get_step_or_404(step_id, db, current_user, check_view=True)

    result = await db.execute(
        select(StepRating).where(
            StepRating.step_id == step_id,
            StepRating.user_id == current_user.id
        )
    )
    rating = result.scalar_one_or_none()
    if not rating:
        raise HTTPException(status_code=404, detail="Rating not found")

    await db.delete(rating)

    # Update aggregated ratings on step
    await _update_step_ratings(db, step)

    await db.commit()
    return None


# Backward-compatible /ratings alias routes
@router.post("/{step_id}/ratings", response_model=StepRatingResponse, status_code=201,
             include_in_schema=False)
async def rate_step_alias(
    step_id: UUID,
    rating_data: StepRatingCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Alias for POST /{step_id}/rate."""
    return await rate_step(step_id, rating_data, db, current_user)


@router.put("/{step_id}/ratings", response_model=StepRatingResponse,
            include_in_schema=False)
async def update_rating_alias(
    step_id: UUID,
    rating_data: StepRatingUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Alias for PUT /{step_id}/rate."""
    return await update_rating(step_id, rating_data, db, current_user)


@router.delete("/{step_id}/ratings", status_code=204,
               include_in_schema=False)
async def delete_rating_alias(
    step_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Alias for DELETE /{step_id}/rate."""
    return await delete_rating(step_id, db, current_user)


@router.get("/{step_id}/reviews", response_model=list[StepRatingResponse])
async def get_reviews(
    step_id: UUID,
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """Get reviews for a step."""
    await get_step_or_404(step_id, db, current_user, check_view=True)

    result = await db.execute(
        select(StepRating).where(
            StepRating.step_id == step_id,
            StepRating.is_visible == True,
            StepRating.review_text.isnot(None)
        ).order_by(desc(StepRating.created_at)).offset(offset).limit(limit)
    )
    ratings = result.scalars().all()

    response = []
    for rating in ratings:
        user_result = await db.execute(
            select(User.name).where(User.id == rating.user_id)
        )
        user_name = user_result.scalar_one_or_none()

        response.append(StepRatingResponse(
            id=rating.id,
            step_id=rating.step_id,
            user_id=rating.user_id,
            rating=rating.rating,
            was_helpful=rating.was_helpful,
            review_text=rating.review_text,
            is_verified_use=rating.is_verified_use,
            session_id=rating.session_id,
            is_visible=rating.is_visible,
            created_at=rating.created_at,
            updated_at=rating.updated_at,
            user_name=user_name,
        ))

    return response


async def _update_step_ratings(db: AsyncSession, step: StepLibrary):
    """Update aggregated rating fields on a step."""
    # Calculate new aggregates
    result = await db.execute(
        select(
            func.count(StepRating.id),
            func.avg(StepRating.rating),
            func.sum(case((StepRating.was_helpful == True, 1), else_=0)),
            func.sum(case((StepRating.was_helpful == False, 1), else_=0))
        ).where(StepRating.step_id == step.id)
    )
    row = result.one()

    step.rating_count = row[0] or 0
    step.rating_average = Decimal(str(round(row[1] or 0, 2)))
    step.helpful_yes = row[2] or 0
    step.helpful_no = row[3] or 0
