import uuid

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user
from app.db.database import get_db
from app.db.models import CanvasState, Design, Edge, Node
from app.schemas.designs import DesignCopy, DesignCreate, DesignResponse, DesignUpdate

router = APIRouter()

# Node.type values that are canvas annotations rather than real devices. Kept in
# sync with the frontend (Sidebar counts, canvasSerializer types).
_GROUP_TYPE = "groupRect"
_TEXT_TYPE = "text"


@router.get("", response_model=list[DesignResponse])
async def list_designs(
    db: AsyncSession = Depends(get_db),
    _: str = Depends(get_current_user),
) -> list[DesignResponse]:
    designs = (await db.execute(select(Design).order_by(Design.created_at))).scalars().all()
    # One grouped query for all designs → node/group/text counts per design.
    rows = (
        await db.execute(select(Node.design_id, Node.type, func.count()).group_by(Node.design_id, Node.type))
    ).all()
    counts: dict[str, dict[str, int]] = {}
    for design_id, node_type, count in rows:
        if design_id is None:
            continue
        bucket = counts.setdefault(design_id, {"node": 0, "group": 0, "text": 0})
        if node_type == _GROUP_TYPE:
            bucket["group"] += count
        elif node_type == _TEXT_TYPE:
            bucket["text"] += count
        else:
            bucket["node"] += count

    result = []
    for d in designs:
        resp = DesignResponse.model_validate(d)
        c = counts.get(d.id, {"node": 0, "group": 0, "text": 0})
        resp.node_count = c["node"]
        resp.group_count = c["group"]
        resp.text_count = c["text"]
        result.append(resp)
    return result


@router.post("", response_model=DesignResponse, status_code=201)
async def create_design(
    body: DesignCreate,
    db: AsyncSession = Depends(get_db),
    _: str = Depends(get_current_user),
) -> DesignResponse:
    design = Design(name=body.name, design_type=body.design_type, icon=body.icon)
    db.add(design)
    await db.flush()
    # Create empty canvas state for the new design
    db.add(CanvasState(design_id=design.id))
    await db.commit()
    await db.refresh(design)
    return DesignResponse.model_validate(design)


@router.post("/{source_id}/copy", response_model=DesignResponse, status_code=201)
async def copy_design(
    source_id: str,
    body: DesignCopy,
    db: AsyncSession = Depends(get_db),
    _: str = Depends(get_current_user),
) -> DesignResponse:
    """Create a new design that deep-copies the source's nodes, edges and canvas state."""
    source = await db.get(Design, source_id)
    if not source:
        raise HTTPException(404, "Source design not found")

    new_design = Design(name=body.name, icon=body.icon, design_type=source.design_type)
    db.add(new_design)
    await db.flush()

    src_nodes = (await db.execute(select(Node).where(Node.design_id == source_id))).scalars().all()
    src_edges = (await db.execute(select(Edge).where(Edge.design_id == source_id))).scalars().all()

    # New id per source node so edges and parent links can be re-pointed at the copy.
    id_map = {n.id: str(uuid.uuid4()) for n in src_nodes}

    # Columns we set explicitly or let the DB default — never copy verbatim.
    node_skip = {"id", "design_id", "parent_id", "created_at", "updated_at"}
    for n in src_nodes:
        cols = {c.name: getattr(n, c.name) for c in Node.__table__.columns if c.name not in node_skip}
        db.add(Node(id=id_map[n.id], design_id=new_design.id, parent_id=None, **cols))
    await db.flush()  # nodes must exist before we wire self-referential parent_id

    # Second pass: re-point parent links inside the copy.
    for n in src_nodes:
        if n.parent_id and n.parent_id in id_map:
            child = await db.get(Node, id_map[n.id])
            if child:
                child.parent_id = id_map[n.parent_id]

    edge_skip = {"id", "design_id", "source", "target", "created_at"}
    for e in src_edges:
        # Skip edges whose endpoints aren't part of this design (dangling FKs).
        if e.source not in id_map or e.target not in id_map:
            continue
        cols = {c.name: getattr(e, c.name) for c in Edge.__table__.columns if c.name not in edge_skip}
        db.add(
            Edge(
                id=str(uuid.uuid4()),
                design_id=new_design.id,
                source=id_map[e.source],
                target=id_map[e.target],
                **cols,
            )
        )

    # Copy canvas state (viewport, custom style, and the floor plan carried in viewport).
    src_state = await db.get(CanvasState, source_id)
    db.add(
        CanvasState(
            design_id=new_design.id,
            viewport=src_state.viewport if src_state else {},
            custom_style=src_state.custom_style if src_state else None,
        )
    )

    await db.commit()
    await db.refresh(new_design)
    return DesignResponse.model_validate(new_design)


@router.put("/{design_id}", response_model=DesignResponse)
async def update_design(
    design_id: str,
    body: DesignUpdate,
    db: AsyncSession = Depends(get_db),
    _: str = Depends(get_current_user),
) -> DesignResponse:
    design = await db.get(Design, design_id)
    if not design:
        raise HTTPException(404, "Design not found")
    if body.name is not None:
        design.name = body.name
    if body.icon is not None:
        design.icon = body.icon
    await db.commit()
    await db.refresh(design)
    return DesignResponse.model_validate(design)


@router.delete("/{design_id}", status_code=204)
async def delete_design(
    design_id: str,
    db: AsyncSession = Depends(get_db),
    _: str = Depends(get_current_user),
) -> None:
    design = await db.get(Design, design_id)
    if not design:
        raise HTTPException(404, "Design not found")
    # Count remaining designs — prevent deleting the last one
    count = (await db.execute(select(Design))).scalars().all()
    if len(count) <= 1:
        raise HTTPException(400, "Cannot delete the only design")
    # Delete associated canvas state, edges, nodes
    cs = await db.get(CanvasState, design_id)
    if cs:
        await db.delete(cs)
    edges = (await db.execute(select(Edge).where(Edge.design_id == design_id))).scalars().all()
    for e in edges:
        await db.delete(e)
    nodes = (await db.execute(select(Node).where(Node.design_id == design_id))).scalars().all()
    for n in nodes:
        await db.delete(n)
    await db.delete(design)
    await db.commit()
