"""
Device management API endpoints.

All routes are tenant-scoped under /api/tenants/{tenant_id}/devices.
RLS is enforced via PostgreSQL — the app_user engine automatically filters
cross-tenant data based on the SET LOCAL app.current_tenant context set by
get_current_user dependency.

RBAC:
- viewer: GET (read-only)
- operator: POST, PUT (write)
- admin/tenant_admin: DELETE
"""

import uuid
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import settings
from app.database import get_db
from app.middleware.rate_limit import limiter
from app.services.audit_service import log_action
from app.middleware.rbac import (
    require_min_role,
    require_operator_or_above,
    require_scope,
    require_tenant_admin_or_above,
)
from app.middleware.tenant_context import CurrentUser, get_current_user
from app.schemas.device import (
    BulkAddRequest,
    BulkAddResult,
    DeviceCreate,
    DeviceListResponse,
    DeviceResponse,
    DeviceUpdate,
    SubnetScanRequest,
    SubnetScanResponse,
)
from app.services import device as device_service
from app.services.scanner import scan_subnet

router = APIRouter(tags=["devices"])


async def _check_tenant_access(
    current_user: CurrentUser, tenant_id: uuid.UUID, db: AsyncSession
) -> None:
    """
    Verify the current user is allowed to access the given tenant.

    - super_admin can access any tenant — re-sets DB tenant context to target tenant.
    - All other roles must match their own tenant_id.
    """
    if current_user.is_super_admin:
        # Re-set tenant context to the target tenant so RLS allows the operation
        from app.database import set_tenant_context
        await set_tenant_context(db, str(tenant_id))
        return
    if current_user.tenant_id != tenant_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access denied: you do not belong to this tenant.",
        )


# ---------------------------------------------------------------------------
# Device CRUD
# ---------------------------------------------------------------------------


@router.get(
    "/tenants/{tenant_id}/devices",
    response_model=DeviceListResponse,
    summary="List devices with pagination and filtering",
    dependencies=[require_scope("devices:read")],
)
async def list_devices(
    tenant_id: uuid.UUID,
    page: int = Query(1, ge=1, description="Page number (1-based)"),
    page_size: int = Query(25, ge=1, le=100, description="Items per page (1-100)"),
    status_filter: Optional[str] = Query(None, alias="status"),
    search: Optional[str] = Query(None, description="Text search on hostname or IP"),
    tag_id: Optional[uuid.UUID] = Query(None),
    group_id: Optional[uuid.UUID] = Query(None),
    sort_by: str = Query("created_at", description="Field to sort by"),
    sort_order: str = Query("desc", description="asc or desc"),
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> DeviceListResponse:
    """List devices for a tenant with optional pagination, filtering, and sorting."""
    await _check_tenant_access(current_user, tenant_id, db)

    items, total = await device_service.get_devices(
        db=db,
        tenant_id=tenant_id,
        page=page,
        page_size=page_size,
        status=status_filter,
        search=search,
        tag_id=tag_id,
        group_id=group_id,
        sort_by=sort_by,
        sort_order=sort_order,
    )
    return DeviceListResponse(items=items, total=total, page=page, page_size=page_size)


@router.post(
    "/tenants/{tenant_id}/devices",
    response_model=DeviceResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Add a device (validates TCP connectivity first)",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("20/minute")
async def create_device(
    request: Request,
    tenant_id: uuid.UUID,
    data: DeviceCreate,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> DeviceResponse:
    """
    Create a new device. Requires operator role or above.

    The device IP/port is TCP-probed before the record is saved.
    Credentials are encrypted with AES-256-GCM before storage and never returned.
    """
    await _check_tenant_access(current_user, tenant_id, db)
    result = await device_service.create_device(
        db=db,
        tenant_id=tenant_id,
        data=data,
        encryption_key=settings.get_encryption_key_bytes(),
    )
    try:
        await log_action(
            db, tenant_id, current_user.user_id, "device_create",
            resource_type="device", resource_id=str(result.id),
            details={"hostname": data.hostname, "ip_address": data.ip_address},
            ip_address=request.client.host if request.client else None,
        )
    except Exception:
        pass
    return result


@router.get(
    "/tenants/{tenant_id}/devices/{device_id}",
    response_model=DeviceResponse,
    summary="Get a single device",
    dependencies=[require_scope("devices:read")],
)
async def get_device(
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> DeviceResponse:
    """Get device details. Viewer role and above."""
    await _check_tenant_access(current_user, tenant_id, db)
    return await device_service.get_device(db=db, tenant_id=tenant_id, device_id=device_id)


@router.put(
    "/tenants/{tenant_id}/devices/{device_id}",
    response_model=DeviceResponse,
    summary="Update a device",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("20/minute")
async def update_device(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    data: DeviceUpdate,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> DeviceResponse:
    """Update device fields. Requires operator role or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    result = await device_service.update_device(
        db=db,
        tenant_id=tenant_id,
        device_id=device_id,
        data=data,
        encryption_key=settings.get_encryption_key_bytes(),
    )
    try:
        await log_action(
            db, tenant_id, current_user.user_id, "device_update",
            resource_type="device", resource_id=str(device_id),
            device_id=device_id,
            details={"changes": data.model_dump(exclude_unset=True)},
            ip_address=request.client.host if request.client else None,
        )
    except Exception:
        pass
    return result


@router.delete(
    "/tenants/{tenant_id}/devices/{device_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete a device",
    dependencies=[Depends(require_tenant_admin_or_above), require_scope("devices:write")],
)
@limiter.limit("5/minute")
async def delete_device(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Hard-delete a device. Requires tenant_admin or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    try:
        await log_action(
            db, tenant_id, current_user.user_id, "device_delete",
            resource_type="device", resource_id=str(device_id),
            device_id=device_id,
            ip_address=request.client.host if request.client else None,
        )
    except Exception:
        pass
    await device_service.delete_device(db=db, tenant_id=tenant_id, device_id=device_id)


# ---------------------------------------------------------------------------
# Subnet scan and bulk add
# ---------------------------------------------------------------------------


@router.post(
    "/tenants/{tenant_id}/devices/scan",
    response_model=SubnetScanResponse,
    summary="Scan a subnet for MikroTik devices",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("5/minute")
async def scan_devices(
    request: Request,
    tenant_id: uuid.UUID,
    data: SubnetScanRequest,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> SubnetScanResponse:
    """
    Scan a CIDR subnet for hosts with open RouterOS API ports (8728/8729).

    Returns a list of discovered IPs for the user to review and selectively
    import — does NOT automatically add devices.

    Requires operator role or above.
    """
    if not current_user.is_super_admin and current_user.tenant_id != tenant_id:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")

    discovered = await scan_subnet(data.cidr)
    import ipaddress
    network = ipaddress.ip_network(data.cidr, strict=False)
    total_scanned = network.num_addresses - 2 if network.num_addresses > 2 else network.num_addresses

    # Audit log the scan (fire-and-forget — never breaks the response)
    try:
        await log_action(
            db, tenant_id, current_user.user_id, "subnet_scan",
            resource_type="network", resource_id=data.cidr,
            details={
                "cidr": data.cidr,
                "devices_found": len(discovered),
                "ip": request.client.host if request.client else None,
            },
            ip_address=request.client.host if request.client else None,
        )
    except Exception:
        pass

    return SubnetScanResponse(
        cidr=data.cidr,
        discovered=discovered,
        total_scanned=total_scanned,
        total_discovered=len(discovered),
    )


@router.post(
    "/tenants/{tenant_id}/devices/bulk-add",
    response_model=BulkAddResult,
    status_code=status.HTTP_201_CREATED,
    summary="Bulk-add devices from scan results",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("5/minute")
async def bulk_add_devices(
    request: Request,
    tenant_id: uuid.UUID,
    data: BulkAddRequest,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> BulkAddResult:
    """
    Add multiple devices at once from scan results.

    Per-device credentials take precedence over shared credentials.
    Devices that fail connectivity checks or validation are reported in `failed`.
    Requires operator role or above.
    """
    await _check_tenant_access(current_user, tenant_id, db)

    added = []
    failed = []
    encryption_key = settings.get_encryption_key_bytes()

    for dev_data in data.devices:
        # Resolve credentials: per-device first, then shared
        username = dev_data.username or data.shared_username
        password = dev_data.password or data.shared_password

        if not username or not password:
            failed.append({
                "ip_address": dev_data.ip_address,
                "error": "No credentials provided (set per-device or shared credentials)",
            })
            continue

        create_data = DeviceCreate(
            hostname=dev_data.hostname or dev_data.ip_address,
            ip_address=dev_data.ip_address,
            api_port=dev_data.api_port,
            api_ssl_port=dev_data.api_ssl_port,
            username=username,
            password=password,
        )

        try:
            device = await device_service.create_device(
                db=db,
                tenant_id=tenant_id,
                data=create_data,
                encryption_key=encryption_key,
            )
            added.append(device)
            try:
                await log_action(
                    db, tenant_id, current_user.user_id, "device_adopt",
                    resource_type="device", resource_id=str(device.id),
                    details={"hostname": create_data.hostname, "ip_address": create_data.ip_address},
                    ip_address=request.client.host if request.client else None,
                )
            except Exception:
                pass
        except HTTPException as exc:
            failed.append({"ip_address": dev_data.ip_address, "error": exc.detail})
        except Exception as exc:
            failed.append({"ip_address": dev_data.ip_address, "error": str(exc)})

    return BulkAddResult(added=added, failed=failed)


# ---------------------------------------------------------------------------
# Group assignment
# ---------------------------------------------------------------------------


@router.post(
    "/tenants/{tenant_id}/devices/{device_id}/groups/{group_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Add device to a group",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("20/minute")
async def add_device_to_group(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    group_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Assign a device to a group. Requires operator or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    await device_service.assign_device_to_group(db, tenant_id, device_id, group_id)


@router.delete(
    "/tenants/{tenant_id}/devices/{device_id}/groups/{group_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Remove device from a group",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("5/minute")
async def remove_device_from_group(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    group_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Remove a device from a group. Requires operator or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    await device_service.remove_device_from_group(db, tenant_id, device_id, group_id)


# ---------------------------------------------------------------------------
# Tag assignment
# ---------------------------------------------------------------------------


@router.post(
    "/tenants/{tenant_id}/devices/{device_id}/tags/{tag_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Add tag to a device",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("20/minute")
async def add_tag_to_device(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    tag_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Assign a tag to a device. Requires operator or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    await device_service.assign_tag_to_device(db, tenant_id, device_id, tag_id)


@router.delete(
    "/tenants/{tenant_id}/devices/{device_id}/tags/{tag_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Remove tag from a device",
    dependencies=[Depends(require_operator_or_above), require_scope("devices:write")],
)
@limiter.limit("5/minute")
async def remove_tag_from_device(
    request: Request,
    tenant_id: uuid.UUID,
    device_id: uuid.UUID,
    tag_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    """Remove a tag from a device. Requires operator or above."""
    await _check_tenant_access(current_user, tenant_id, db)
    await device_service.remove_tag_from_device(db, tenant_id, device_id, tag_id)
