"""Maintenance windows API endpoints.

Tenant-scoped routes under /api/tenants/{tenant_id}/ for:
- Maintenance window CRUD (list, create, update, delete)
- Filterable by status: upcoming, active, past

RLS enforced via get_db() (app_user engine with tenant context).
RBAC: operator and above for all operations.
"""

import json
import logging
import uuid
from datetime import datetime
from typing import Any, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from pydantic import BaseModel, ConfigDict
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db, set_tenant_context
from app.middleware.rate_limit import limiter
from app.middleware.tenant_context import CurrentUser, get_current_user

logger = logging.getLogger(__name__)

router = APIRouter(tags=["maintenance-windows"])


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


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."""
    if current_user.is_super_admin:
        await set_tenant_context(db, str(tenant_id))
    elif current_user.tenant_id != tenant_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access denied to this tenant",
        )


def _require_operator(current_user: CurrentUser) -> None:
    """Raise 403 if user does not have at least operator role."""
    if current_user.role == "viewer":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Requires at least operator role.",
        )


# ---------------------------------------------------------------------------
# Request/response schemas
# ---------------------------------------------------------------------------


class MaintenanceWindowCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str
    device_ids: list[str] = []
    start_at: datetime
    end_at: datetime
    suppress_alerts: bool = True
    notes: Optional[str] = None


class MaintenanceWindowUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: Optional[str] = None
    device_ids: Optional[list[str]] = None
    start_at: Optional[datetime] = None
    end_at: Optional[datetime] = None
    suppress_alerts: Optional[bool] = None
    notes: Optional[str] = None


class MaintenanceWindowResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")
    id: str
    tenant_id: str
    name: str
    device_ids: list[str]
    start_at: str
    end_at: str
    suppress_alerts: bool
    notes: Optional[str] = None
    created_by: Optional[str] = None
    created_at: str


# ---------------------------------------------------------------------------
# CRUD endpoints
# ---------------------------------------------------------------------------


@router.get(
    "/tenants/{tenant_id}/maintenance-windows",
    summary="List maintenance windows for tenant",
)
async def list_maintenance_windows(
    tenant_id: uuid.UUID,
    window_status: Optional[str] = Query(None, alias="status"),
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> list[dict[str, Any]]:
    await _check_tenant_access(current_user, tenant_id, db)
    _require_operator(current_user)

    filters = ["1=1"]
    params: dict[str, Any] = {}

    if window_status == "active":
        filters.append("mw.start_at <= NOW() AND mw.end_at >= NOW()")
    elif window_status == "upcoming":
        filters.append("mw.start_at > NOW()")
    elif window_status == "past":
        filters.append("mw.end_at < NOW()")

    where = " AND ".join(filters)

    result = await db.execute(
        text(f"""
            SELECT mw.id, mw.tenant_id, mw.name, mw.device_ids,
                   mw.start_at, mw.end_at, mw.suppress_alerts,
                   mw.notes, mw.created_by, mw.created_at
            FROM maintenance_windows mw
            WHERE {where}
            ORDER BY mw.start_at DESC
        """),
        params,
    )

    return [
        {
            "id": str(row[0]),
            "tenant_id": str(row[1]),
            "name": row[2],
            "device_ids": row[3] if isinstance(row[3], list) else [],
            "start_at": row[4].isoformat() if row[4] else None,
            "end_at": row[5].isoformat() if row[5] else None,
            "suppress_alerts": row[6],
            "notes": row[7],
            "created_by": str(row[8]) if row[8] else None,
            "created_at": row[9].isoformat() if row[9] else None,
        }
        for row in result.fetchall()
    ]


@router.post(
    "/tenants/{tenant_id}/maintenance-windows",
    summary="Create maintenance window",
    status_code=status.HTTP_201_CREATED,
)
@limiter.limit("20/minute")
async def create_maintenance_window(
    request: Request,
    tenant_id: uuid.UUID,
    body: MaintenanceWindowCreate,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict[str, Any]:
    await _check_tenant_access(current_user, tenant_id, db)
    _require_operator(current_user)

    if body.end_at <= body.start_at:
        raise HTTPException(422, "end_at must be after start_at")

    window_id = str(uuid.uuid4())

    await db.execute(
        text("""
            INSERT INTO maintenance_windows
                (id, tenant_id, name, device_ids, start_at, end_at,
                 suppress_alerts, notes, created_by)
            VALUES
                (CAST(:id AS uuid), CAST(:tenant_id AS uuid),
                 :name, CAST(:device_ids AS jsonb), :start_at, :end_at,
                 :suppress_alerts, :notes, CAST(:created_by AS uuid))
        """),
        {
            "id": window_id,
            "tenant_id": str(tenant_id),
            "name": body.name,
            "device_ids": json.dumps(body.device_ids),
            "start_at": body.start_at,
            "end_at": body.end_at,
            "suppress_alerts": body.suppress_alerts,
            "notes": body.notes,
            "created_by": str(current_user.user_id),
        },
    )
    await db.commit()

    return {
        "id": window_id,
        "tenant_id": str(tenant_id),
        "name": body.name,
        "device_ids": body.device_ids,
        "start_at": body.start_at.isoformat(),
        "end_at": body.end_at.isoformat(),
        "suppress_alerts": body.suppress_alerts,
        "notes": body.notes,
        "created_by": str(current_user.user_id),
        "created_at": datetime.utcnow().isoformat(),
    }


@router.put(
    "/tenants/{tenant_id}/maintenance-windows/{window_id}",
    summary="Update maintenance window",
)
@limiter.limit("20/minute")
async def update_maintenance_window(
    request: Request,
    tenant_id: uuid.UUID,
    window_id: uuid.UUID,
    body: MaintenanceWindowUpdate,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict[str, Any]:
    await _check_tenant_access(current_user, tenant_id, db)
    _require_operator(current_user)

    # Build dynamic SET clause for partial updates
    set_parts: list[str] = ["updated_at = NOW()"]
    params: dict[str, Any] = {"window_id": str(window_id)}

    if body.name is not None:
        set_parts.append("name = :name")
        params["name"] = body.name
    if body.device_ids is not None:
        set_parts.append("device_ids = CAST(:device_ids AS jsonb)")
        params["device_ids"] = json.dumps(body.device_ids)
    if body.start_at is not None:
        set_parts.append("start_at = :start_at")
        params["start_at"] = body.start_at
    if body.end_at is not None:
        set_parts.append("end_at = :end_at")
        params["end_at"] = body.end_at
    if body.suppress_alerts is not None:
        set_parts.append("suppress_alerts = :suppress_alerts")
        params["suppress_alerts"] = body.suppress_alerts
    if body.notes is not None:
        set_parts.append("notes = :notes")
        params["notes"] = body.notes

    set_clause = ", ".join(set_parts)

    result = await db.execute(
        text(f"""
            UPDATE maintenance_windows
            SET {set_clause}
            WHERE id = CAST(:window_id AS uuid)
            RETURNING id, tenant_id, name, device_ids, start_at, end_at,
                      suppress_alerts, notes, created_by, created_at
        """),
        params,
    )
    row = result.fetchone()
    if not row:
        raise HTTPException(404, "Maintenance window not found")
    await db.commit()

    return {
        "id": str(row[0]),
        "tenant_id": str(row[1]),
        "name": row[2],
        "device_ids": row[3] if isinstance(row[3], list) else [],
        "start_at": row[4].isoformat() if row[4] else None,
        "end_at": row[5].isoformat() if row[5] else None,
        "suppress_alerts": row[6],
        "notes": row[7],
        "created_by": str(row[8]) if row[8] else None,
        "created_at": row[9].isoformat() if row[9] else None,
    }


@router.delete(
    "/tenants/{tenant_id}/maintenance-windows/{window_id}",
    summary="Delete maintenance window",
    status_code=status.HTTP_204_NO_CONTENT,
)
@limiter.limit("5/minute")
async def delete_maintenance_window(
    request: Request,
    tenant_id: uuid.UUID,
    window_id: uuid.UUID,
    current_user: CurrentUser = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    await _check_tenant_access(current_user, tenant_id, db)
    _require_operator(current_user)

    result = await db.execute(
        text("DELETE FROM maintenance_windows WHERE id = CAST(:id AS uuid) RETURNING id"),
        {"id": str(window_id)},
    )
    if not result.fetchone():
        raise HTTPException(404, "Maintenance window not found")
    await db.commit()
