"""Tags plugin routes — CRUD, assign, bulk-assign, entity counts."""

from __future__ import annotations

import uuid

from fastapi import APIRouter, Body, Depends, HTTPException, Response, status
from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.db import get_db
from app.deps import get_current_user, require_permission
from app.plugins.builtins.tags.models import Tag, TagAssignment
from app.plugins.builtins.tags.schemas import (
    TagAssignRequest,
    TagBulkAssignRequest,
    TagCreate,
    TagUnassignRequest,
    TagUpdate,
)

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

VALID_ENTITY_TYPES = {"contact", "file", "folder"}


def _parse_uuid(val: str, field: str) -> uuid.UUID:
    try:
        return uuid.UUID(val)
    except (ValueError, TypeError):
        raise HTTPException(
            400, detail={"detail": f"Invalid {field}", "code": "invalid_id"}
        ) from None


@router.get("", dependencies=[Depends(require_permission("tags:read"))])
async def list_tags(
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """List all tags with entity counts."""
    tenant_id = uuid.UUID(current_user["tenant_id"])

    # Subquery for entity counts per tag
    count_sq = (
        select(
            TagAssignment.tag_id,
            func.count(TagAssignment.id).label("entity_count"),
        )
        .where(TagAssignment.tenant_id == tenant_id)
        .group_by(TagAssignment.tag_id)
        .subquery()
    )

    result = await db.execute(
        select(Tag, func.coalesce(count_sq.c.entity_count, 0))
        .outerjoin(count_sq, Tag.id == count_sq.c.tag_id)
        .where(Tag.tenant_id == tenant_id)
        .order_by(Tag.name)
    )
    rows = result.all()

    return [
        {
            "id": str(tag.id),
            "name": tag.name,
            "color": tag.color,
            "entity_count": count,
        }
        for tag, count in rows
    ]


@router.post("", status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_permission("tags:write"))])
async def create_tag(
    body: TagCreate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Create a new tag."""
    tenant_id = uuid.UUID(current_user["tenant_id"])

    # Check name uniqueness within tenant
    existing = await db.execute(
        select(Tag).where(Tag.tenant_id == tenant_id, Tag.name == body.name)
    )
    if existing.scalar_one_or_none() is not None:
        raise HTTPException(409, detail={"detail": "Tag name already exists", "code": "duplicate"})

    tag = Tag(tenant_id=tenant_id, name=body.name, color=body.color)
    db.add(tag)
    await db.flush()
    return {
        "id": str(tag.id),
        "name": tag.name,
        "color": tag.color,
        "entity_count": 0,
    }


@router.patch("/{tag_id}", dependencies=[Depends(require_permission("tags:write"))])
async def update_tag(
    tag_id: str,
    body: TagUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Update a tag (name and/or color)."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    tid = _parse_uuid(tag_id, "tag_id")

    result = await db.execute(select(Tag).where(Tag.id == tid, Tag.tenant_id == tenant_id))
    tag = result.scalar_one_or_none()
    if tag is None:
        raise HTTPException(404, detail={"detail": "Tag not found", "code": "not_found"})

    data = body.model_dump(exclude_unset=True)
    if "name" in data:
        # Check uniqueness if name is changing
        if data["name"] != tag.name:
            dup = await db.execute(
                select(Tag).where(Tag.tenant_id == tenant_id, Tag.name == data["name"])
            )
            if dup.scalar_one_or_none() is not None:
                raise HTTPException(
                    409, detail={"detail": "Tag name already exists", "code": "duplicate"}
                )
        tag.name = data["name"]
    if "color" in data:
        tag.color = data["color"]

    await db.flush()
    return {
        "id": str(tag.id),
        "name": tag.name,
        "color": tag.color,
        "entity_count": 0,
    }


@router.post("/assign", dependencies=[Depends(require_permission("tags:write"))])
async def assign_tag(
    body: TagAssignRequest,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Assign a tag to an entity."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    tag_id = _parse_uuid(body.tag_id, "tag_id")
    entity_id = _parse_uuid(body.entity_id, "entity_id")

    if body.entity_type not in VALID_ENTITY_TYPES:
        raise HTTPException(
            400, detail={"detail": "Invalid entity_type", "code": "invalid_entity_type"}
        )

    # Verify tag exists
    tag_result = await db.execute(select(Tag).where(Tag.id == tag_id, Tag.tenant_id == tenant_id))
    if tag_result.scalar_one_or_none() is None:
        raise HTTPException(404, detail={"detail": "Tag not found", "code": "tag_not_found"})

    # Check if already assigned
    existing = await db.execute(
        select(TagAssignment).where(
            TagAssignment.tenant_id == tenant_id,
            TagAssignment.tag_id == tag_id,
            TagAssignment.entity_type == body.entity_type,
            TagAssignment.entity_id == entity_id,
        )
    )
    if existing.scalar_one_or_none() is not None:
        return {
            "id": str(tag_id),
            "tag_id": str(tag_id),
            "entity_type": body.entity_type,
            "entity_id": str(entity_id),
            "already_assigned": True,
        }

    assignment = TagAssignment(
        tenant_id=tenant_id,
        tag_id=tag_id,
        entity_type=body.entity_type,
        entity_id=entity_id,
    )
    db.add(assignment)
    await db.flush()
    return {
        "id": str(assignment.id),
        "tag_id": str(tag_id),
        "entity_type": body.entity_type,
        "entity_id": str(entity_id),
        "already_assigned": False,
    }


@router.delete("/assign", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(require_permission("tags:delete"))])
async def unassign_tag(
    body: TagUnassignRequest = Body(...),
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Remove a tag assignment from an entity."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    tag_id = _parse_uuid(body.tag_id, "tag_id")
    entity_id = _parse_uuid(body.entity_id, "entity_id")

    result = await db.execute(
        select(TagAssignment).where(
            TagAssignment.tenant_id == tenant_id,
            TagAssignment.tag_id == tag_id,
            TagAssignment.entity_type == body.entity_type,
            TagAssignment.entity_id == entity_id,
        )
    )
    assignment = result.scalar_one_or_none()
    if assignment is None:
        raise HTTPException(404, detail={"detail": "Assignment not found", "code": "not_found"})

    await db.delete(assignment)
    return Response(status_code=status.HTTP_204_NO_CONTENT)


@router.delete("/{tag_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(require_permission("tags:delete"))])
async def delete_tag(
    tag_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Delete a tag and cascade-delete all its assignments."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    tid = _parse_uuid(tag_id, "tag_id")

    result = await db.execute(select(Tag).where(Tag.id == tid, Tag.tenant_id == tenant_id))
    tag = result.scalar_one_or_none()
    if tag is None:
        raise HTTPException(404, detail={"detail": "Tag not found", "code": "not_found"})

    # Cascade delete assignments
    await db.execute(
        delete(TagAssignment).where(
            TagAssignment.tag_id == tid, TagAssignment.tenant_id == tenant_id
        )
    )
    await db.delete(tag)
    return Response(status_code=status.HTTP_204_NO_CONTENT)


@router.post("/bulk-assign", dependencies=[Depends(require_permission("tags:write"))])
async def bulk_assign_tags(
    body: TagBulkAssignRequest,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """Assign multiple tags to a single entity."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    entity_id = _parse_uuid(body.entity_id, "entity_id")

    if body.entity_type not in VALID_ENTITY_TYPES:
        raise HTTPException(
            400, detail={"detail": "Invalid entity_type", "code": "invalid_entity_type"}
        )

    tag_ids = [_parse_uuid(tid, "tag_id") for tid in body.tag_ids]

    # Verify all tags exist
    for tid in tag_ids:
        result = await db.execute(select(Tag).where(Tag.id == tid, Tag.tenant_id == tenant_id))
        if result.scalar_one_or_none() is None:
            raise HTTPException(
                404, detail={"detail": f"Tag {tid} not found", "code": "tag_not_found"}
            )

    assigned = []
    already = []
    for tid in tag_ids:
        existing = await db.execute(
            select(TagAssignment).where(
                TagAssignment.tenant_id == tenant_id,
                TagAssignment.tag_id == tid,
                TagAssignment.entity_type == body.entity_type,
                TagAssignment.entity_id == entity_id,
            )
        )
        if existing.scalar_one_or_none() is not None:
            already.append(str(tid))
            continue

        assignment = TagAssignment(
            tenant_id=tenant_id,
            tag_id=tid,
            entity_type=body.entity_type,
            entity_id=entity_id,
        )
        db.add(assignment)
        await db.flush()
        assigned.append(str(assignment.id))

    return {
        "assigned": assigned,
        "already_assigned": already,
        "total": len(assigned) + len(already),
    }


@router.get("/{tag_id}/entities", dependencies=[Depends(require_permission("tags:read"))])
async def list_tag_entities(
    tag_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    """List all entities assigned to a tag."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    tid = _parse_uuid(tag_id, "tag_id")

    # Verify tag exists
    tag_result = await db.execute(select(Tag).where(Tag.id == tid, Tag.tenant_id == tenant_id))
    if tag_result.scalar_one_or_none() is None:
        raise HTTPException(404, detail={"detail": "Tag not found", "code": "not_found"})

    result = await db.execute(
        select(TagAssignment).where(
            TagAssignment.tenant_id == tenant_id,
            TagAssignment.tag_id == tid,
        )
    )
    assignments = result.scalars().all()
    return [
        {
            "entity_type": a.entity_type,
            "entity_id": str(a.entity_id),
        }
        for a in assignments
    ]
