import uuid
from datetime import UTC, datetime

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

from src.db import get_db
from src.models.site import Site
from src.models.site_group import SiteGroup
from src.schemas.auth import CurrentUser
from src.schemas.site_group import SiteGroupCreate, SiteGroupResponse, SiteGroupUpdate
from src.services.dependencies import require_role

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


@router.post("/", response_model=SiteGroupResponse, status_code=status.HTTP_201_CREATED)
async def create_site_group(
    body: SiteGroupCreate,
    current_user: CurrentUser = Depends(require_role("owner", "admin")),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Create a new site group within the current organisation."""
    # Check name uniqueness within the org
    existing = await db.execute(
        select(SiteGroup).where(
            SiteGroup.organisation_id == current_user.organisation_id,
            SiteGroup.name == body.name,
            SiteGroup.deleted_at.is_(None),
        )
    )
    if existing.scalar_one_or_none() is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Site group '{body.name}' already exists in this organisation",
        )

    group = SiteGroup(
        organisation_id=current_user.organisation_id,
        name=body.name,
        description=body.description,
    )
    db.add(group)
    await db.flush()
    await db.refresh(group)
    return _to_response(group, site_count=0)


@router.get("/", response_model=list[SiteGroupResponse])
async def list_site_groups(
    current_user: CurrentUser = Depends(require_role("owner", "admin", "editor", "viewer")),
    db: AsyncSession = Depends(get_db),
) -> list[dict]:
    """List all site groups in the current organisation with site counts."""
    # Subquery for site counts
    site_count_sq = (
        select(
            Site.site_group_id,
            func.count(Site.id).label("cnt"),
        )
        .where(Site.deleted_at.is_(None))
        .group_by(Site.site_group_id)
        .subquery()
    )

    result = await db.execute(
        select(SiteGroup, func.coalesce(site_count_sq.c.cnt, 0).label("site_count"))
        .outerjoin(site_count_sq, SiteGroup.id == site_count_sq.c.site_group_id)
        .where(
            SiteGroup.organisation_id == current_user.organisation_id,
            SiteGroup.deleted_at.is_(None),
        )
        .order_by(SiteGroup.name)
    )

    return [_to_response(row.SiteGroup, site_count=row.site_count) for row in result.all()]


@router.get("/{group_id}", response_model=SiteGroupResponse)
async def get_site_group(
    group_id: uuid.UUID,
    current_user: CurrentUser = Depends(require_role("owner", "admin", "editor", "viewer")),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Get a specific site group by ID."""
    group = await _get_org_group(group_id, current_user.organisation_id, db)
    site_count = await _count_sites(group_id, db)
    return _to_response(group, site_count=site_count)


@router.patch("/{group_id}", response_model=SiteGroupResponse)
async def update_site_group(
    group_id: uuid.UUID,
    body: SiteGroupUpdate,
    current_user: CurrentUser = Depends(require_role("owner", "admin", "editor")),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Update a site group's name or description."""
    group = await _get_org_group(group_id, current_user.organisation_id, db)

    update_data = body.model_dump(exclude_unset=True)

    # Check name uniqueness if name is being changed
    if "name" in update_data and update_data["name"] != group.name:
        existing = await db.execute(
            select(SiteGroup).where(
                SiteGroup.organisation_id == current_user.organisation_id,
                SiteGroup.name == update_data["name"],
                SiteGroup.deleted_at.is_(None),
                SiteGroup.id != group_id,
            )
        )
        if existing.scalar_one_or_none() is not None:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Site group '{update_data['name']}' already exists",
            )

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

    await db.flush()
    await db.refresh(group)
    site_count = await _count_sites(group_id, db)
    return _to_response(group, site_count=site_count)


@router.delete("/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_site_group(
    group_id: uuid.UUID,
    current_user: CurrentUser = Depends(require_role("owner", "admin")),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Soft-delete a site group. Sites in this group become ungrouped."""
    group = await _get_org_group(group_id, current_user.organisation_id, db)

    # Ungroup all sites in this group
    result = await db.execute(
        select(Site).where(
            Site.site_group_id == group_id,
            Site.deleted_at.is_(None),
        )
    )
    for site in result.scalars().all():
        site.site_group_id = None

    group.deleted_at = datetime.now(UTC)
    await db.flush()


# ── Helpers ──────────────────────────────────────────────────────────


async def _get_org_group(
    group_id: uuid.UUID,
    organisation_id: uuid.UUID,
    db: AsyncSession,
) -> SiteGroup:
    """Fetch a site group ensuring it belongs to the given organisation."""
    result = await db.execute(
        select(SiteGroup).where(
            SiteGroup.id == group_id,
            SiteGroup.organisation_id == organisation_id,
            SiteGroup.deleted_at.is_(None),
        )
    )
    group = result.scalar_one_or_none()
    if group is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Site group not found",
        )
    return group


async def _count_sites(group_id: uuid.UUID, db: AsyncSession) -> int:
    """Count active sites in a group."""
    result = await db.execute(
        select(func.count(Site.id)).where(
            Site.site_group_id == group_id,
            Site.deleted_at.is_(None),
        )
    )
    return result.scalar_one()


def _to_response(group: SiteGroup, *, site_count: int) -> dict:
    """Convert a SiteGroup model to a response dict with site_count."""
    return {
        "id": group.id,
        "organisation_id": group.organisation_id,
        "name": group.name,
        "description": group.description,
        "created_at": group.created_at,
        "updated_at": group.updated_at,
        "site_count": site_count,
    }
