"""
FastAPI application for Sentinel.
"""

from dataclasses import asdict
import sqlite3
import os
import smtplib
import asyncio
from email.message import EmailMessage
from email.utils import formataddr
from pathlib import Path
from datetime import datetime, timedelta

from fastapi import FastAPI, HTTPException, Request, Query
from pydantic import BaseModel, Field
from fastapi.responses import RedirectResponse, FileResponse, Response
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from config.settings import (
    BRAIN_HISTORY_DATABASE_PATH,
    DATABASE_PATH,
)
from sentinel.brain.engine import SentinelBrain
from sentinel.brain.history import BrainHistoryService
from sentinel.brain.equipment_profile import EquipmentProfileBuilder
from sentinel.database.manager import DatabaseManager
from sentinel.monitoring.store import MonitoringStore, ROLE_DEFINITIONS
from sentinel.v6_services import V6Services
from sentinel.brain.expert_report import ExpertReportBuilder
from sentinel.cloud.store import CloudStore
from sentinel.notifications.templates import NotificationTemplateEngine


app = FastAPI(
    title="Sentinel API",
    version="7.0.1",
)

database = DatabaseManager(DATABASE_PATH)
brain = SentinelBrain()
brain_history = BrainHistoryService(
    BRAIN_HISTORY_DATABASE_PATH,
)
monitoring_store = MonitoringStore(
    Path(DATABASE_PATH).parent / "sentinel_monitoring.db",
)
monitoring_store.initialize()

PROJECT_ROOT = Path(__file__).resolve().parents[2]

def _load_sentinel_env() -> None:
    """Load persistent Sentinel settings from .env without overriding real environment variables."""
    env_path = PROJECT_ROOT / ".env"
    if not env_path.exists():
        return
    for raw_line in env_path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip().strip('"').strip("'")
        if key.startswith("SENTINEL_") and key not in os.environ:
            os.environ[key] = value

_load_sentinel_env()
v6_services = V6Services(PROJECT_ROOT, Path(DATABASE_PATH).parent)
expert_report_builder = ExpertReportBuilder()
cloud_store = CloudStore(Path(DATABASE_PATH).parent / "sentinel_cloud.db")
cloud_store.initialize()
notification_templates = NotificationTemplateEngine()

templates = Jinja2Templates(
    directory=PROJECT_ROOT / "templates",
)

app.mount(
    "/static",
    StaticFiles(directory=PROJECT_ROOT / "static"),
    name="static",
)


class UserPayload(BaseModel):
    """Create/update user payload."""
    email: str
    display_name: str
    role_code: str = "VIEWER"
    enabled: bool = True


class GroupPayload(BaseModel):
    """Create/update group payload."""
    group_code: str
    display_name: str
    site_code: str | None = None
    description: str | None = None
    enabled: bool = True


class EquipmentGroupPayload(BaseModel):
    """Equipment/group assignment payload."""
    equipment_id: str
    group_id: int


class UserGroupPayload(BaseModel):
    """User/group permission assignment payload."""
    user_id: int
    group_id: int
    can_view: bool = True
    can_acknowledge: bool = False
    can_manage_alarms: bool = False


class AlarmRulePayload(BaseModel):
    """Executable temperature-rule payload."""
    name: str
    scope_type: str
    scope_id: str
    category: str = "THRESHOLD"
    severity: str = "WARNING"
    metric: str = "internal_temperature_c"
    operator: str = "above"
    threshold: float
    sensor_id: str | None = None
    delay_minutes: int = Field(default=0, ge=0, le=1440)
    hysteresis: float = Field(default=0.0, ge=0.0)
    enabled: bool = True


class AlarmRecipientPayload(BaseModel):
    rule_id: int
    step_order: int = Field(default=1, ge=1, le=20)
    channel: str = "EMAIL"
    recipient: str
    delay_minutes: int = Field(default=0, ge=0, le=1440)


class AlarmSchedulePayload(BaseModel):
    """Suppression/maintenance window payload."""
    name: str
    scope_type: str
    scope_id: str
    starts_at: str
    ends_at: str
    reason: str
    alarm_category: str | None = None
    alarm_code: str | None = None
    timezone: str = "Europe/Paris"
    created_by: str | None = None
    enabled: bool = True




class CalibrationPayload(BaseModel):
    """Calibration record payload linked to one registered sensor."""
    sensor_id: str = Field(min_length=1)
    sentinel_value: float
    reference_value: float
    reference_instrument: str | None = None
    technician: str = Field(min_length=1)
    comment: str | None = None


class AlarmActionPayload(BaseModel):
    """Alarm acknowledgement/resolution payload."""
    actor: str = Field(min_length=1)
    comment: str = Field(min_length=1)


class CloudDevicePayload(BaseModel):
    device_id: str = Field(min_length=1)
    equipment_id: str = Field(min_length=1)
    site_code: str | None = None
    display_name: str | None = None
    edge_version: str | None = None
    heartbeat_interval_seconds: int = Field(default=15, ge=5, le=3600)
    metadata: dict[str, object] = Field(default_factory=dict)


class CloudMeasurementBatch(BaseModel):
    batch_id: str = Field(min_length=1)
    device_id: str = Field(min_length=1)
    equipment_id: str = Field(min_length=1)
    measurements: list[dict[str, object]]


def _cloud_api_authorized(request: Request) -> bool:
    expected = os.getenv("SENTINEL_CLOUD_API_KEY", "").strip()
    if not expected:
        return True
    return request.headers.get("X-Sentinel-Key", "") == expected


def _device_alert_recipients() -> list[str]:
    return [item.strip() for item in os.getenv("SENTINEL_DEVICE_ALERT_EMAILS", "").split(",") if item.strip()]


def _handle_device_transition(device_id: str, equipment_id: str, event_type: str, detail: str = "") -> None:
    code = f"DEVICE-OFFLINE:{device_id}"
    if event_type == "DEVICE_OFFLINE":
        alarm_id = monitoring_store.open_or_update_alarm(
            equipment_id=equipment_id, category="DEVICE", severity="CRITICAL", code=code,
            title="Sentinel Device offline",
            message=detail or f"No heartbeat received from {device_id}",
            suppressed=False, suppression_schedule_id=None,
        )
        for recipient in _device_alert_recipients():
            monitoring_store.queue_direct_notification(alarm_id=alarm_id, recipient=recipient, channel="EMAIL")
        process_alarm_notifications()
    elif event_type == "DEVICE_ONLINE":
        monitoring_store.clear_resolution_lock(equipment_id, code)
        monitoring_store.auto_resolve_alarm(
            equipment_id=equipment_id, code=code, comment=f"Heartbeat restored from {device_id}",
        )


async def _cloud_heartbeat_watch_loop() -> None:
    while True:
        try:
            grace = int(os.getenv("SENTINEL_HEARTBEAT_GRACE_SECONDS", "180"))
            for event in cloud_store.sweep_offline(grace_seconds=grace):
                _handle_device_transition(
                    str(event["device_id"]), str(event["equipment_id"]), "DEVICE_OFFLINE",
                    f"No heartbeat since {event.get('last_heartbeat_at')}",
                )
        except Exception as exc:
            print(f"Sentinel V7 heartbeat monitor error: {exc}")
        await asyncio.sleep(max(10, int(os.getenv("SENTINEL_HEARTBEAT_CHECK_SECONDS", "30"))))


@app.on_event("startup")
async def _start_cloud_monitor() -> None:
    if os.getenv("SENTINEL_DISABLE_HEARTBEAT_MONITOR", "0") != "1":
        app.state.cloud_monitor_task = asyncio.create_task(_cloud_heartbeat_watch_loop())


@app.on_event("shutdown")
async def _stop_cloud_monitor() -> None:
    task = getattr(app.state, "cloud_monitor_task", None)
    if task:
        task.cancel()


def _equipment_ids_for_rule(
    rule: dict[str, object],
    fleet: list[dict[str, object]],
) -> list[str]:
    """Resolve a rule scope into equipment ids."""
    scope_type = str(rule["scope_type"]).upper()
    scope_id = str(rule["scope_id"])

    if scope_type == "EQUIPMENT":
        return [scope_id]

    if scope_type == "GROUP":
        groups = monitoring_store.get_groups()
        matching = next(
            (
                group
                for group in groups
                if str(group["group_code"]) == scope_id
                or str(group["id"]) == scope_id
            ),
            None,
        )
        if not matching:
            return []
        return monitoring_store.get_group_equipment_ids(
            int(matching["id"])
        )

    # SITE currently means all equipment in this Sentinel instance.
    return [
        str(item["equipment_id"])
        for item in fleet
    ]


def _notification_transport_status() -> dict[str, object]:
    return {
        "email_configured": bool(os.getenv("SENTINEL_SMTP_HOST") and os.getenv("SENTINEL_SMTP_FROM")),
        "sms_configured": False,
        "smtp_host": os.getenv("SENTINEL_SMTP_HOST", ""),
        "smtp_from": os.getenv("SENTINEL_SMTP_FROM", ""),
    }

def _send_email_notification(row: dict[str, object]) -> tuple[bool, str]:
    host = os.getenv("SENTINEL_SMTP_HOST")
    sender = os.getenv("SENTINEL_SMTP_FROM")
    if not host or not sender:
        return False, "SMTP transport not configured"
    port = int(os.getenv("SENTINEL_SMTP_PORT", "587"))
    user = os.getenv("SENTINEL_SMTP_USERNAME") or os.getenv("SENTINEL_SMTP_USER")
    password = os.getenv("SENTINEL_SMTP_PASSWORD")
    use_tls = os.getenv("SENTINEL_SMTP_TLS", "1") != "0"
    subject, body = notification_templates.build(row)
    msg = EmailMessage()
    msg["Subject"] = subject
    from_name = os.getenv("SENTINEL_SMTP_FROM_NAME", "Sentinel Cold")
    msg["From"] = formataddr((from_name, sender))
    msg["To"] = str(row.get("recipient"))
    msg.set_content(body)
    try:
        with smtplib.SMTP(host, port, timeout=15) as smtp:
            smtp.ehlo()
            if use_tls:
                smtp.starttls()
                smtp.ehlo()
            if user:
                smtp.login(user, password or "")
            smtp.send_message(msg)
        return True, "E-mail sent by SMTP"
    except Exception as exc:
        return False, f"SMTP error: {exc}"

def process_alarm_notifications() -> None:
    now_iso = datetime.now().isoformat(timespec="seconds")
    for row in monitoring_store.pending_notifications(now_iso):
        if str(row.get("state")).upper() != "ACTIVE":
            monitoring_store.mark_notification(int(row["id"]), status="CANCELLED", detail="Alarm no longer active")
            continue
        channel = str(row.get("channel") or "").upper()
        if channel in {"EMAIL", "BOTH"}:
            ok, detail = _send_email_notification(row)
            if ok and channel == "EMAIL":
                monitoring_store.mark_notification(int(row["id"]), status="SENT", detail=detail)
                continue
            if not ok:
                # Keep a truthful visible state: configured cascade exists but transport is unavailable.
                monitoring_store.mark_notification(int(row["id"]), status="WAITING_TRANSPORT", detail=detail)
                continue
        if channel in {"SMS", "BOTH"}:
            monitoring_store.mark_notification(int(row["id"]), status="WAITING_TRANSPORT", detail="SMS gateway not configured")


def evaluate_alarm_rules() -> None:
    """Evaluate enabled alarm rules against current real measurements."""
    fleet = database.get_equipment_fleet()
    fleet_ids = {
        str(item["equipment_id"])
        for item in fleet
    }
    now_iso = datetime.now().isoformat(timespec="seconds")

    for rule in monitoring_store.get_alarm_rules():
        if not int(rule["enabled"]):
            continue

        parameters = rule.get("parameters") or {}
        metric = str(parameters.get("metric", "internal_temperature_c"))
        operator = str(parameters.get("operator", "above"))
        threshold = float(parameters.get("threshold", 0.0))
        rule_id = int(rule["id"])

        for equipment_id in _equipment_ids_for_rule(rule, fleet):
            if equipment_id not in fleet_ids:
                continue

            measurement = database.get_latest_equipment_measurement(
                equipment_id=equipment_id,
            )
            if not measurement:
                continue

            fleet_item = next((item for item in fleet if str(item.get("equipment_id")) == equipment_id), {})
            raw_value = fleet_item.get("health_score") if metric == "health_score" else measurement.get(metric)
            if raw_value is None:
                continue

            try:
                value = float(raw_value)
            except (TypeError, ValueError):
                continue

            raw_violated = (value > threshold if operator == "above" else value < threshold)
            delay_minutes = max(0, int(parameters.get("delay_minutes", 0) or 0))
            hysteresis = max(0.0, float(parameters.get("hysteresis", 0.0) or 0.0))
            violated = raw_violated
            if raw_violated and delay_minutes > 0:
                cutoff = datetime.now() - timedelta(minutes=delay_minutes)
                history = database.get_equipment_measurement_history(equipment_id, limit=500)
                relevant = []
                for row in history:
                    try:
                        ts = datetime.fromisoformat(str(row.get("recorded_at", "")).replace("Z", "+00:00")).replace(tzinfo=None)
                    except ValueError:
                        continue
                    if ts >= cutoff and row.get(metric) is not None:
                        relevant.append((ts, float(row[metric])))
                # Alarm only after the whole requested period is represented and continuously violated.
                if not relevant or relevant[0][0] > cutoff + timedelta(minutes=max(2, delay_minutes * 0.25)):
                    violated = False
                else:
                    violated = all((v > threshold if operator == "above" else v < threshold) for _, v in relevant)

            alarm_code = f"RULE-{rule_id}"
            if violated:
                if monitoring_store.is_resolution_locked(equipment_id, alarm_code):
                    continue
                schedule = monitoring_store.active_suppression_for(
                    equipment_id=equipment_id,
                    category=str(rule["category"]),
                    alarm_code=alarm_code,
                    now_iso=now_iso,
                )
                comparator = ">" if operator == "above" else "<"
                alarm_id = monitoring_store.open_or_update_alarm(
                    equipment_id=equipment_id,
                    category=str(rule["category"]),
                    severity=str(rule["severity"]),
                    code=alarm_code,
                    title=str(rule["name"]),
                    message=(
                        f"{metric} = {value:.3f} "
                        f"{comparator} {threshold:.3f}"
                    ),
                    suppressed=schedule is not None,
                    suppression_schedule_id=(
                        int(schedule["id"])
                        if schedule is not None
                        else None
                    ),
                )
                if schedule is None:
                    monitoring_store.queue_alarm_notifications(alarm_id=alarm_id, rule_id=rule_id)
            elif not raw_violated:
                cleared = (
                    value <= (threshold - hysteresis)
                    if operator == "above"
                    else value >= (threshold + hysteresis)
                )
                if cleared:
                    monitoring_store.clear_resolution_lock(equipment_id, alarm_code)
                    monitoring_store.auto_resolve_alarm(
                        equipment_id=equipment_id, code=alarm_code,
                        comment=f"Condition cleared with hysteresis: {metric}={value:.3f}",
                    )
    process_alarm_notifications()


def get_equipment_or_404(
    equipment_id: str,
) -> dict[str, object]:
    """
    Return the latest equipment measurement or raise HTTP 404.
    """
    measurement = database.get_latest_equipment_measurement(
        equipment_id=equipment_id,
    )

    if measurement is None:
        raise HTTPException(
            status_code=404,
            detail="Equipment not found.",
        )

    return measurement


def analyze_equipment_history(
    equipment_id: str,
):
    """
    Analyze the recent history of one equipment.
    """
    history = database.get_equipment_measurement_history(
        equipment_id=equipment_id,
        limit=720,
    )

    if not history:
        raise HTTPException(
            status_code=404,
            detail="No measurement history available.",
        )


    analysis = brain.analyze_history(history)

    latest_measurement = history[-1]

    brain_history.save_analysis(
        equipment_id=equipment_id,
        measurement_id=int(
            latest_measurement["id"]
        ),
        measurement_recorded_at=str(
            latest_measurement["recorded_at"]
        ),
        analysis_result=analysis,
    )

    return analysis


@app.get("/")
def operations_dashboard(request: Request):
    """Display the Sentinel operational dashboard."""
    evaluate_alarm_rules()
    context = _fleet_context()
    alarms = monitoring_store.get_alarms(limit=250)

    daily: dict[str, dict[str, int]] = {}
    for alarm in alarms:
        opened = str(alarm.get("opened_at") or "")[:10]
        if not opened:
            continue
        daily.setdefault(opened, {"total": 0, "resolved": 0})
        daily[opened]["total"] += 1
        if str(alarm.get("state")).upper() == "RESOLVED":
            daily[opened]["resolved"] += 1

    alarm_daily = [
        {
            "date": date,
            "total": values["total"],
            "resolved": values["resolved"],
            "open": values["total"] - values["resolved"],
        }
        for date, values in sorted(daily.items())[-7:]
    ]

    context.update(
        {
            "request": request,
            "page": "dashboard",
            "alarms": alarms[:8],
            "alarm_daily": alarm_daily,
            "groups": monitoring_store.get_groups(),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/equipment-overview")
def equipment_overview(
    request: Request,
    group: str | None = Query(default=None),
    status: str | None = Query(default=None),
):
    """Display the operational equipment overview."""
    context = _fleet_context()
    equipment = list(context["equipment"])

    if status in {"healthy", "attention", "offline"}:
        if status == "healthy":
            equipment = [item for item in equipment if item.get("health_score") is not None and float(item["health_score"]) >= 85.0]
        elif status == "attention":
            equipment = [item for item in equipment if item.get("health_score") is not None and float(item["health_score"]) < 85.0]
        else:
            equipment = [item for item in equipment if item.get("recorded_at") is None]

    if group:
        equipment = [
            item for item in equipment
            if any(
                str(entry["group_code"]) == group
                for entry in item.get("groups", [])
            )
        ]

    context.update(
        {
            "request": request,
            "page": "equipment",
            "equipment": equipment,
            "groups": monitoring_store.get_groups(),
            "selected_group": group,
            "selected_status": status,
            "alarms": monitoring_store.get_alarms(limit=250),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/alarm-blocking")
def alarm_blocking_console(request: Request):
    """Display the alarm suppression calendar."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "alarm-blocking",
            "groups": monitoring_store.get_groups(),
            "alarm_schedules": monitoring_store.get_alarm_schedules(),
            "group_memberships": monitoring_store.get_group_memberships(),
            "equipment_group_map": monitoring_store.get_equipment_group_map(),
            "notification_transport": _notification_transport_status(),
            "sensors": v6_services.sensors(),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/organization")
def organization_console(request: Request):
    """Display the organization tree."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "organization",
            "groups": monitoring_store.get_groups(),
            "users": monitoring_store.get_users(),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/users")
def users_console(request: Request):
    """Display user and permission management."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "users",
            "groups": monitoring_store.get_groups(),
            "users": monitoring_store.get_users(),
            "roles": ROLE_DEFINITIONS,
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/audit")
def audit_console(request: Request):
    """Display the operational audit log."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "audit",
            "events": monitoring_store.get_recent_events(limit=500),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="operations_console.html",
        context=context,
    )


@app.get("/dashboard")
def legacy_dashboard_redirect():
    """
    Redirect the previous dashboard URL.
    """
    return RedirectResponse(
        url="/equipment/freezer-test-001",
        status_code=307,
    )


@app.get("/equipment/{equipment_id}")
def equipment_dashboard(
    request: Request,
    equipment_id: str,
):
    """
    Display one equipment dashboard.
    """
    measurement = database.get_latest_equipment_measurement(equipment_id=equipment_id)
    if measurement is None:
        fleet_item = next((item for item in database.get_equipment_fleet() if str(item.get("equipment_id")) == equipment_id), None)
        if fleet_item is None:
            raise HTTPException(status_code=404, detail="Equipment not found.")
        return templates.TemplateResponse(
            request=request, name="equipment_offline.html",
            context={"request": request, "equipment": fleet_item, "groups": monitoring_store.get_equipment_group_map().get(equipment_id, [])},
        )

    analysis = analyze_equipment_history(
        equipment_id=equipment_id,
    )
    v6_services.ensure_default_sensors(
        equipment_id=equipment_id,
        device_id=str(measurement.get("device_id") or "unknown-device"),
        simulated=str(measurement.get("device_id") or "").startswith("sim-"),
    )

    return templates.TemplateResponse(
        request=request,
        name="dashboard.html",
        context={
            "request": request,
            "measurement": measurement,
            "analysis": analysis,
            "calibrations": v6_services.calibrations(equipment_id),
            "sensors": v6_services.sensors(equipment_id),
            "calibratable_sensors": v6_services.sensors(equipment_id, calibratable_only=True),
            "diagnostics": v6_services.diagnostics(
                DATABASE_PATH, monitoring_store.database_path
            ),
        },
    )



def _fleet_context() -> dict[str, object]:
    """Build shared fleet monitoring context."""
    equipment = database.get_equipment_fleet()
    alarm_summary = monitoring_store.get_alarm_summary()

    healthy = sum(
        1
        for item in equipment
        if (
            item.get("health_score") is not None
            and float(item["health_score"]) >= 85.0
        )
    )

    attention = sum(
        1
        for item in equipment
        if (
            item.get("health_score") is not None
            and float(item["health_score"]) < 85.0
        )
    )

    offline = sum(
        1
        for item in equipment
        if item.get("recorded_at") is None
    )

    equipment_group_map = monitoring_store.get_equipment_group_map()

    for item in equipment:
        item["groups"] = equipment_group_map.get(item.get("equipment_id"), [])
        item["simulated"] = str(item.get("device_id") or "").startswith("sim-")
        v6_services.ensure_default_sensors(
            equipment_id=str(item["equipment_id"]),
            device_id=str(item.get("device_id") or "unknown-device"),
            simulated=bool(item["simulated"]),
        )

    return {
        "equipment": equipment,
        "alarm_summary": alarm_summary,
        "healthy_count": healthy,
        "attention_count": attention,
        "offline_count": offline,
    }


@app.get("/alarms")
def alarms_console(
    request: Request,
    group: str | None = Query(default=None),
):
    """Display the live alarm console."""
    evaluate_alarm_rules()
    context = _fleet_context()
    alarm_rows = monitoring_store.get_alarms(group_code=group, limit=200)
    context.update(
        {
            "request": request,
            "page": "alarms",
            "groups": monitoring_store.get_groups(),
            "selected_group": group,
            "alarms": alarm_rows,
            "alarm_events_map": monitoring_store.alarm_events_for([int(a["id"]) for a in alarm_rows]),
            "alarm_notifications": monitoring_store.get_alarm_notifications(),
            "notification_transport": _notification_transport_status(),
        }
    )

    return templates.TemplateResponse(
        request=request,
        name="monitoring_console.html",
        context=context,
    )


@app.get("/trends")
def trends_console(request: Request):
    """Display fleet trend exploration."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "trends",
        }
    )

    return templates.TemplateResponse(
        request=request,
        name="monitoring_console.html",
        context=context,
    )


@app.get("/reports")
def reports_console(request: Request):
    """Display the report center."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "reports",
        }
    )

    return templates.TemplateResponse(
        request=request,
        name="monitoring_console.html",
        context=context,
    )


@app.get("/events")
def events_console(request: Request):
    """Display the monitoring event log."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "events",
            "events": monitoring_store.get_recent_events(
                limit=250,
            ),
        }
    )

    return templates.TemplateResponse(
        request=request,
        name="monitoring_console.html",
        context=context,
    )


def _administration_response(request: Request):
    """Render the single V6 administration workspace."""
    context = _fleet_context()
    context.update(
        {
            "request": request,
            "page": "administration",
            "users": monitoring_store.get_users(),
            "roles": ROLE_DEFINITIONS,
            "groups": monitoring_store.get_groups(),
            "alarm_rules": monitoring_store.get_alarm_rules(),
            "alarm_recipients": monitoring_store.get_alarm_rule_recipients(),
            "alarm_schedules": monitoring_store.get_alarm_schedules(),
            "group_memberships": monitoring_store.get_group_memberships(),
            "equipment_group_map": monitoring_store.get_equipment_group_map(),
            "notification_transport": _notification_transport_status(),
        }
    )
    return templates.TemplateResponse(
        request=request,
        name="monitoring_console.html",
        context=context,
    )


@app.get("/administration")
def administration_console(request: Request):
    """Render the central Sentinel V6 administration workspace."""
    return _administration_response(request)


@app.get("/configuration")
def configuration_console(request: Request):
    """Compatibility route for the V6 administration workspace."""
    return _administration_response(request)



@app.get("/api/system/version")
def system_version() -> dict[str, object]:
    """Return the loaded Sentinel backend version."""
    return {
        "version": "7.0.1",
        "release": "V7 Cloud + Edge Foundation",
        "functional_admin": True,
        "functional_reports": True,
        "functional_maintenance": True,
        "cloud_edge": True,
        "heartbeat_monitor": True,
        "store_and_forward": True,
        "notification_templates": True,
        "internal_ups_required": False,
    }

@app.post("/api/users")
def create_user(payload: UserPayload) -> dict[str, object]:
    """Create one user."""
    try:
        user_id = monitoring_store.create_user(
            email=payload.email,
            display_name=payload.display_name,
            role_code=payload.role_code,
        )
    except (ValueError, sqlite3.IntegrityError) as error:
        raise HTTPException(status_code=409, detail=str(error)) from error
    return {"ok": True, "id": user_id}


@app.put("/api/users/{user_id}")
def update_user(
    user_id: int,
    payload: UserPayload,
) -> dict[str, object]:
    """Update one user."""
    try:
        monitoring_store.update_user(
            user_id,
            email=payload.email,
            display_name=payload.display_name,
            role_code=payload.role_code,
            enabled=payload.enabled,
        )
    except (ValueError, sqlite3.IntegrityError) as error:
        raise HTTPException(status_code=409, detail=str(error)) from error
    return {"ok": True}


@app.delete("/api/users/{user_id}")
def delete_user(user_id: int) -> dict[str, object]:
    """Delete one user."""
    monitoring_store.delete_user(user_id)
    return {"ok": True}


@app.post("/api/groups")
def create_group(payload: GroupPayload) -> dict[str, object]:
    """Create one group."""
    try:
        group_id = monitoring_store.create_group(
            group_code=payload.group_code,
            display_name=payload.display_name,
            site_code=payload.site_code,
            description=payload.description,
        )
    except sqlite3.IntegrityError as error:
        raise HTTPException(status_code=409, detail=str(error)) from error
    return {"ok": True, "id": group_id}


@app.put("/api/groups/{group_id}")
def update_group(
    group_id: int,
    payload: GroupPayload,
) -> dict[str, object]:
    """Update one group."""
    monitoring_store.update_group(
        group_id,
        group_code=payload.group_code,
        display_name=payload.display_name,
        site_code=payload.site_code,
        description=payload.description,
        enabled=payload.enabled,
    )
    return {"ok": True}


@app.delete("/api/groups/{group_id}")
def delete_group(group_id: int) -> dict[str, object]:
    """Delete one group."""
    monitoring_store.delete_group(group_id)
    return {"ok": True}


@app.post("/api/groups/equipment")
def assign_equipment_to_group(
    payload: EquipmentGroupPayload,
) -> dict[str, object]:
    """Assign equipment to group."""
    get_equipment_or_404(payload.equipment_id)
    monitoring_store.assign_equipment_to_group(
        equipment_id=payload.equipment_id,
        group_id=payload.group_id,
    )
    return {"ok": True}


@app.delete("/api/groups/{group_id}/equipment/{equipment_id}")
def remove_equipment_from_group(
    group_id: int,
    equipment_id: str,
) -> dict[str, object]:
    """Remove equipment from group."""
    monitoring_store.remove_equipment_from_group(
        equipment_id=equipment_id,
        group_id=group_id,
    )
    return {"ok": True}


@app.post("/api/groups/users")
def assign_user_to_group(
    payload: UserGroupPayload,
) -> dict[str, object]:
    """Assign user to group with permissions."""
    monitoring_store.assign_user_to_group(
        user_id=payload.user_id,
        group_id=payload.group_id,
        can_view=payload.can_view,
        can_acknowledge=payload.can_acknowledge,
        can_manage_alarms=payload.can_manage_alarms,
    )
    return {"ok": True}


@app.delete("/api/groups/{group_id}/users/{user_id}")
def remove_user_from_group(
    group_id: int,
    user_id: int,
) -> dict[str, object]:
    """Remove user from group."""
    monitoring_store.remove_user_from_group(
        user_id=user_id,
        group_id=group_id,
    )
    return {"ok": True}


@app.post("/api/alarm-rules")
def create_alarm_rule(
    payload: AlarmRulePayload,
) -> dict[str, object]:
    """Create one executable alarm rule."""
    try:
        rule_id = monitoring_store.create_alarm_rule(
            name=payload.name,
            scope_type=payload.scope_type,
            scope_id=payload.scope_id,
            category=payload.category,
            severity=payload.severity,
            metric=payload.metric,
            operator=payload.operator,
            threshold=payload.threshold,
            sensor_id=payload.sensor_id,
            delay_minutes=payload.delay_minutes,
            hysteresis=payload.hysteresis,
        )
    except ValueError as error:
        raise HTTPException(status_code=400, detail=str(error)) from error
    evaluate_alarm_rules()
    return {"ok": True, "id": rule_id}


@app.put("/api/alarm-rules/{rule_id}")
def update_alarm_rule(
    rule_id: int,
    payload: AlarmRulePayload,
) -> dict[str, object]:
    """Update one executable alarm rule."""
    monitoring_store.update_alarm_rule(
        rule_id,
        name=payload.name,
        scope_type=payload.scope_type,
        scope_id=payload.scope_id,
        category=payload.category,
        severity=payload.severity,
        metric=payload.metric,
        operator=payload.operator,
        threshold=payload.threshold,
        enabled=payload.enabled,
        sensor_id=payload.sensor_id,
        delay_minutes=payload.delay_minutes,
        hysteresis=payload.hysteresis,
    )
    evaluate_alarm_rules()
    return {"ok": True}


@app.delete("/api/alarm-rules/{rule_id}")
def delete_alarm_rule(rule_id: int) -> dict[str, object]:
    """Delete one alarm rule."""
    monitoring_store.delete_alarm_rule(rule_id)
    return {"ok": True}


@app.post("/api/alarm-recipients")
def add_alarm_recipient(payload: AlarmRecipientPayload) -> dict[str, object]:
    try:
        rid=monitoring_store.add_alarm_rule_recipient(rule_id=payload.rule_id,step_order=payload.step_order,channel=payload.channel,recipient=payload.recipient,delay_minutes=payload.delay_minutes)
    except ValueError as error:
        raise HTTPException(status_code=400,detail=str(error)) from error
    return {"ok": True, "id": rid}

@app.delete("/api/alarm-recipients/{recipient_id}")
def delete_alarm_recipient(recipient_id: int) -> dict[str, object]:
    monitoring_store.delete_alarm_rule_recipient(recipient_id)
    return {"ok": True}


@app.post("/api/alarm-schedules")
def create_alarm_schedule(
    payload: AlarmSchedulePayload,
) -> dict[str, object]:
    """Create one alarm suppression schedule."""
    try:
        schedule_id = monitoring_store.create_alarm_schedule(
            name=payload.name,
            scope_type=payload.scope_type,
            scope_id=payload.scope_id,
            starts_at=payload.starts_at,
            ends_at=payload.ends_at,
            reason=payload.reason,
            alarm_category=payload.alarm_category,
            alarm_code=payload.alarm_code,
            timezone=payload.timezone,
            created_by=payload.created_by,
        )
    except ValueError as error:
        raise HTTPException(status_code=400, detail=str(error)) from error
    evaluate_alarm_rules()
    return {"ok": True, "id": schedule_id}


@app.put("/api/alarm-schedules/{schedule_id}")
def replace_alarm_schedule(schedule_id: int, payload: AlarmSchedulePayload) -> dict[str, object]:
    """Edit all fields of a maintenance/deviation window."""
    monitoring_store.replace_alarm_schedule(
        schedule_id, name=payload.name, scope_type=payload.scope_type, scope_id=payload.scope_id,
        starts_at=payload.starts_at, ends_at=payload.ends_at, reason=payload.reason,
        alarm_category=payload.alarm_category, alarm_code=payload.alarm_code,
        timezone=payload.timezone, created_by=payload.created_by, enabled=payload.enabled,
    )
    evaluate_alarm_rules()
    return {"ok": True}

@app.put("/api/alarm-schedules/{schedule_id}/enabled")
def set_alarm_schedule_enabled(
    schedule_id: int,
    enabled: bool,
) -> dict[str, object]:
    """Enable or disable one schedule."""
    monitoring_store.update_alarm_schedule(
        schedule_id,
        enabled=enabled,
    )
    evaluate_alarm_rules()
    return {"ok": True}


@app.delete("/api/alarm-schedules/{schedule_id}")
def delete_alarm_schedule(
    schedule_id: int,
) -> dict[str, object]:
    """Delete one schedule."""
    monitoring_store.delete_alarm_schedule(schedule_id)
    evaluate_alarm_rules()
    return {"ok": True}


@app.post("/api/alarms/{alarm_id}/acknowledge")
def acknowledge_alarm(
    alarm_id: int,
    payload: AlarmActionPayload,
) -> dict[str, object]:
    """Acknowledge one active alarm."""
    try:
        monitoring_store.acknowledge_alarm(
            alarm_id,
            actor=payload.actor,
            comment=payload.comment,
        )
    except ValueError as error:
        raise HTTPException(status_code=404, detail=str(error)) from error
    return {"ok": True}


@app.post("/api/alarms/{alarm_id}/resolve")
def resolve_alarm(
    alarm_id: int,
    payload: AlarmActionPayload,
) -> dict[str, object]:
    """Resolve one alarm."""
    try:
        monitoring_store.resolve_alarm(
            alarm_id,
            actor=payload.actor,
            comment=payload.comment,
        )
    except ValueError as error:
        raise HTTPException(status_code=404, detail=str(error)) from error
    return {"ok": True}

@app.get("/measurements/latest")
def read_latest_measurement() -> dict[str, object]:
    """
    Return the most recent Sentinel measurement.
    """
    measurement = database.get_latest_measurement()

    if measurement is None:
        raise HTTPException(
            status_code=404,
            detail="No measurement available.",
        )

    return measurement


@app.get("/measurements/history")
def read_measurement_history(
    limit: int = 200,
) -> list[dict[str, object]]:
    """
    Return recent measurements from all equipment.

    This legacy endpoint is kept for compatibility.
    """
    return database.get_recent_measurements(
        limit=limit,
    )


@app.get("/equipment/{equipment_id}/measurements/latest")
def read_equipment_latest_measurement(
    equipment_id: str,
) -> dict[str, object]:
    """
    Return the latest measurement for one equipment.
    """
    return get_equipment_or_404(
        equipment_id=equipment_id,
    )


@app.get("/equipment/{equipment_id}/measurements/history")
def read_equipment_measurement_history(
    equipment_id: str,
    limit: int = 720,
) -> list[dict[str, object]]:
    """
    Return recent measurements for one equipment.
    """
    get_equipment_or_404(
        equipment_id=equipment_id,
    )

    return database.get_equipment_measurement_history(
        equipment_id=equipment_id,
        limit=limit,
    )


@app.get("/equipment/{equipment_id}/analysis")
def read_equipment_analysis(
    equipment_id: str,
) -> dict[str, object]:
    """
    Return historical Sentinel Brain analysis for one equipment.
    """
    get_equipment_or_404(
        equipment_id=equipment_id,
    )

    analysis = analyze_equipment_history(
        equipment_id=equipment_id,
    )

    return asdict(analysis)


@app.get("/equipment/{equipment_id}/profile")
def read_equipment_profile(
    equipment_id: str,
) -> dict[str, object]:
    # Return the Sentinel DNA profile for one equipment.
    get_equipment_or_404(
        equipment_id=equipment_id,
    )

    history = database.get_equipment_measurement_history(
        equipment_id=equipment_id,
        limit=5000,
    )

    try:
        profile = EquipmentProfileBuilder.build(
            history=history,
        )
    except ValueError as error:
        raise HTTPException(
            status_code=409,
            detail=str(error),
        ) from error

    return profile.to_dict()


@app.get("/equipment/{equipment_id}/brain/history")
def read_brain_history(
    equipment_id: str,
    limit: int = 500,
) -> list[dict[str, object]]:
    # Return stored Brain diagnostics for one equipment.
    get_equipment_or_404(
        equipment_id=equipment_id,
    )

    return brain_history.get_history(
        equipment_id=equipment_id,
        limit=limit,
    )


@app.get("/equipment/{equipment_id}/brain/trend")
def read_brain_trend(
    equipment_id: str,
    limit: int = 500,
) -> dict[str, object]:
    # Return the Health Score trend from Brain History.
    get_equipment_or_404(
        equipment_id=equipment_id,
    )

    return brain_history.get_health_trend(
        equipment_id=equipment_id,
        limit=limit,
    )



@app.get("/api/system/diagnostics")
def system_diagnostics() -> dict[str, object]:
    """Run local database and disk diagnostics."""
    return v6_services.diagnostics(DATABASE_PATH, monitoring_store.database_path)


@app.get("/api/system/backup")
def download_system_backup():
    """Create and download a full operational Sentinel backup."""
    path = v6_services.build_backup()
    return FileResponse(
        path=path,
        media_type="application/zip",
        filename=path.name,
    )


@app.post("/api/equipment/{equipment_id}/calibrations")
def create_calibration(equipment_id: str, payload: CalibrationPayload) -> dict[str, object]:
    """Record a reference comparison for one equipment sensor."""
    get_equipment_or_404(equipment_id)
    try:
        calibration_id = v6_services.record_calibration(
            equipment_id=equipment_id, sensor_id=payload.sensor_id,
            sentinel_value=payload.sentinel_value, reference_value=payload.reference_value,
            reference_instrument=payload.reference_instrument, technician=payload.technician,
            comment=payload.comment,
        )
    except ValueError as error:
        raise HTTPException(status_code=400, detail=str(error)) from error
    return {"ok": True, "id": calibration_id}


@app.get("/api/equipment/{equipment_id}/calibrations")
def list_calibrations(equipment_id: str) -> list[dict[str, object]]:
    """Return calibration history for one equipment."""
    get_equipment_or_404(equipment_id)
    return v6_services.calibrations(equipment_id)


@app.get("/api/equipment/{equipment_id}/sensors")
def list_equipment_sensors(equipment_id: str) -> list[dict[str, object]]:
    """Return concrete registered sensors for an equipment."""
    measurement=get_equipment_or_404(equipment_id)
    v6_services.ensure_default_sensors(equipment_id, str(measurement.get("device_id") or "unknown-device"), str(measurement.get("device_id") or "").startswith("sim-"))
    return v6_services.sensors(equipment_id)

@app.post("/api/simulation/demo-fleet")
def create_demo_fleet() -> dict[str, object]:
    """Create/update nine clearly identified simulated assets with real history rows."""
    import math, random
    profiles = [
        ("sim-freezer-002", -20.0, 96, "healthy"),
        ("sim-freezer-003", -80.0, 93, "healthy"),
        ("sim-freezer-004", -20.0, 78, "warming_drift"),
        ("sim-freezer-005", -80.0, 72, "compressor_hot"),
        ("sim-fridge-006", 4.0, 90, "healthy"),
        ("sim-fridge-007", 5.0, 64, "temperature_alarm"),
        ("sim-freezer-008", -20.0, 82, "cycling"),
        ("sim-freezer-009", -80.0, 55, "predictive"),
        ("sim-freezer-010", -20.0, 0, "offline"),
    ]
    rng=random.Random(6001)
    now=datetime.now()
    for idx,(equipment_id,setpoint,score,profile) in enumerate(profiles, start=2):
        device_id=f"sim-pi-{idx:03d}"
        database.register_device(device_id,equipment_id)
        v6_services.ensure_default_sensors(equipment_id,device_id,True)
        if profile == "offline":
            continue
        for n in range(96):
            phase=n/95
            internal=setpoint + rng.uniform(-0.35,0.35)
            compressor=42 + 5*math.sin(n/7) + rng.uniform(-1,1)
            health=float(score)
            if profile=="warming_drift": internal += 5.5*phase; health=92-14*phase
            elif profile=="compressor_hot": compressor += 24*phase; health=91-19*phase
            elif profile=="temperature_alarm": internal = setpoint + 7.5*phase; health=88-24*phase
            elif profile=="cycling": compressor += 8*math.sin(n*1.7); health=88-6*phase
            elif profile=="predictive": compressor += 18*phase; internal += 2.5*phase; health=90-35*phase
            recorded=(now-timedelta(minutes=(95-n)*15)).isoformat(timespec="seconds")
            with database._connect() as connection:
                connection.execute(
                    """INSERT INTO measurements(recorded_at,device_id,equipment_id,ambient_temperature_c,internal_temperature_c,evaporator_temperature_c,compressor_temperature_c,compressor_current_a,health_score) VALUES(?,?,?,?,?,?,?,?,?)""",
                    (recorded,device_id,equipment_id,22+rng.uniform(-.6,.6),internal,internal-6,compressor,1.2+rng.uniform(-.15,.15),health),
                )
                connection.commit()
    return {"ok": True, "simulated_equipment": len(profiles), "message": "Demo fleet ready"}

@app.delete("/api/simulation/demo-fleet")
def clear_demo_fleet() -> dict[str, object]:
    """Remove only assets explicitly created by Sentinel simulation mode."""
    with database._connect() as connection:
        connection.execute("DELETE FROM measurements WHERE device_id LIKE 'sim-%'")
        connection.execute("DELETE FROM devices WHERE device_id LIKE 'sim-%'")
        connection.commit()
    with sqlite3.connect(v6_services.maintenance_db) as connection:
        connection.execute("DELETE FROM sensors WHERE simulated=1")
        connection.commit()
    return {"ok": True}

@app.get("/cloud")
def cloud_console(request: Request):
    """V7 central Device connectivity and synchronization console."""
    grace = int(os.getenv("SENTINEL_HEARTBEAT_GRACE_SECONDS", "180"))
    for event in cloud_store.sweep_offline(grace_seconds=grace):
        _handle_device_transition(str(event["device_id"]), str(event["equipment_id"]), "DEVICE_OFFLINE", f"No heartbeat since {event.get('last_heartbeat_at')}")
    return templates.TemplateResponse(
        request=request, name="cloud_console.html",
        context={
            "request": request,
            "devices": cloud_store.devices(),
            "events": cloud_store.events(limit=100),
            "stats": cloud_store.stats(),
            "heartbeat_grace_seconds": grace,
            "cloud_api_key_required": bool(os.getenv("SENTINEL_CLOUD_API_KEY", "").strip()),
        },
    )


@app.get("/api/v7/cloud/status")
def cloud_status() -> dict[str, object]:
    return {
        "ok": True,
        "version": "7.0.1",
        "mode": os.getenv("SENTINEL_MODE", "hybrid"),
        "stats": cloud_store.stats(),
        "heartbeat_grace_seconds": int(os.getenv("SENTINEL_HEARTBEAT_GRACE_SECONDS", "180")),
        "device_alert_email_configured": bool(_device_alert_recipients()),
    }


@app.get("/api/v7/devices")
def cloud_devices() -> list[dict[str, object]]:
    return cloud_store.devices()


@app.get("/api/v7/device-events")
def cloud_device_events(limit: int = 250) -> list[dict[str, object]]:
    return cloud_store.events(limit=limit)


@app.post("/api/v7/devices/register")
def cloud_register_device(payload: CloudDevicePayload, request: Request) -> dict[str, object]:
    if not _cloud_api_authorized(request):
        raise HTTPException(status_code=401, detail="Invalid Sentinel cloud key")
    transition = cloud_store.register_device(
        device_id=payload.device_id, equipment_id=payload.equipment_id, site_code=payload.site_code,
        display_name=payload.display_name, edge_version=payload.edge_version,
        heartbeat_interval_seconds=payload.heartbeat_interval_seconds,
        last_ip=request.client.host if request.client else None, metadata=payload.metadata,
    )
    database.register_device(payload.device_id, payload.equipment_id)
    if transition:
        _handle_device_transition(payload.device_id, payload.equipment_id, transition)
    return {"ok": True, "transition": transition}


@app.post("/api/v7/devices/heartbeat")
def cloud_heartbeat(payload: CloudDevicePayload, request: Request) -> dict[str, object]:
    if not _cloud_api_authorized(request):
        raise HTTPException(status_code=401, detail="Invalid Sentinel cloud key")
    transition = cloud_store.heartbeat(
        device_id=payload.device_id, equipment_id=payload.equipment_id, edge_version=payload.edge_version,
        heartbeat_interval_seconds=payload.heartbeat_interval_seconds,
        last_ip=request.client.host if request.client else None, metadata=payload.metadata,
    )
    database.register_device(payload.device_id, payload.equipment_id)
    if transition:
        _handle_device_transition(payload.device_id, payload.equipment_id, transition)
    return {"ok": True, "server_time": datetime.now().isoformat(timespec="seconds"), "transition": transition}


@app.post("/api/v7/sync/measurements")
def cloud_sync_measurements(payload: CloudMeasurementBatch, request: Request) -> dict[str, object]:
    if not _cloud_api_authorized(request):
        raise HTTPException(status_code=401, detail="Invalid Sentinel cloud key")
    if cloud_store.batch_exists(payload.batch_id):
        return {"ok": True, "batch_id": payload.batch_id, "accepted": 0, "duplicates": len(payload.measurements), "already_received": True}
    accepted = 0
    duplicates = 0
    database.register_device(payload.device_id, payload.equipment_id)
    for measurement in payload.measurements[:5000]:
        row = dict(measurement)
        row.setdefault("device_id", payload.device_id)
        row.setdefault("equipment_id", payload.equipment_id)
        try:
            inserted = cloud_store.ingest_measurement(row)
        except ValueError as error:
            raise HTTPException(status_code=400, detail=str(error)) from error
        if not inserted:
            duplicates += 1
            continue
        accepted += 1
        database.insert_measurement_at(
            recorded_at=str(row.get("recorded_at")), device_id=str(row.get("device_id")), equipment_id=str(row.get("equipment_id")),
            ambient_temperature_c=row.get("ambient_temperature_c"), internal_temperature_c=row.get("internal_temperature_c"),
            evaporator_temperature_c=row.get("evaporator_temperature_c"), compressor_temperature_c=row.get("compressor_temperature_c"),
            compressor_current_a=row.get("compressor_current_a"), health_score=row.get("health_score"),
        )
    cloud_store.record_batch(payload.batch_id, payload.device_id, accepted, duplicates)
    transition = cloud_store.heartbeat(device_id=payload.device_id, equipment_id=payload.equipment_id, edge_version="7.0.1")
    if transition:
        _handle_device_transition(payload.device_id, payload.equipment_id, transition)
    return {"ok": True, "batch_id": payload.batch_id, "accepted": accepted, "duplicates": duplicates}


@app.post("/api/v7/cloud/sweep")
def cloud_sweep(request: Request) -> dict[str, object]:
    if not _cloud_api_authorized(request):
        raise HTTPException(status_code=401, detail="Invalid Sentinel cloud key")
    grace = int(os.getenv("SENTINEL_HEARTBEAT_GRACE_SECONDS", "180"))
    events = cloud_store.sweep_offline(grace_seconds=grace)
    for event in events:
        _handle_device_transition(str(event["device_id"]), str(event["equipment_id"]), "DEVICE_OFFLINE", f"No heartbeat since {event.get('last_heartbeat_at')}")
    return {"ok": True, "offline_transitions": len(events), "events": events}


@app.get("/reports/print/{equipment_id}")
def printable_equipment_report(request: Request, equipment_id: str):
    get_equipment_or_404(equipment_id)
    analysis=analyze_equipment_history(equipment_id)
    return templates.TemplateResponse(request=request,name="report_print.html",context={"request":request,"equipment_id":equipment_id,"analysis":analysis,"generated_at":datetime.now().isoformat(timespec="minutes")})


@app.get("/reports/export/monitoring/{equipment_id}.csv")
def export_monitoring_report(equipment_id: str):
    """Download the measurement history as CSV."""
    get_equipment_or_404(equipment_id)
    rows = database.get_equipment_measurement_history(equipment_id, limit=5000)
    return Response(
        content=v6_services.csv_bytes(rows),
        media_type="text/csv; charset=utf-8",
        headers={"Content-Disposition": f'attachment; filename="{equipment_id}_monitoring.csv"'},
    )


@app.get("/reports/export/alarms/{equipment_id}.csv")
def export_alarm_report(equipment_id: str):
    """Download alarm history as CSV."""
    get_equipment_or_404(equipment_id)
    rows = [row for row in monitoring_store.get_alarms(limit=5000) if row["equipment_id"] == equipment_id]
    return Response(
        content=v6_services.csv_bytes(rows),
        media_type="text/csv; charset=utf-8",
        headers={"Content-Disposition": f'attachment; filename="{equipment_id}_alarms.csv"'},
    )


@app.get("/reports/export/technical/{equipment_id}.json")
def export_technical_report(equipment_id: str, language: str = "en"):
    """Download the current explainable Brain report as JSON."""
    analysis = analyze_equipment_history(equipment_id)
    report = expert_report_builder.build(equipment_id, analysis, language=language)
    return Response(
        content=v6_services.json_bytes(report.to_dict()),
        media_type="application/json; charset=utf-8",
        headers={"Content-Disposition": f'attachment; filename="{equipment_id}_technical_report.json"'},
    )


@app.get("/reports/export/predictive/{equipment_id}.json")
def export_predictive_report(equipment_id: str):
    """Download Brain analysis and long-term trend as JSON."""
    analysis = analyze_equipment_history(equipment_id)
    trend = brain_history.get_health_trend(equipment_id=equipment_id, limit=500)
    payload = {
        "equipment_id": equipment_id,
        "generated_at": datetime.now().isoformat(timespec="seconds"),
        "analysis": asdict(analysis),
        "brain_trend": trend,
    }
    return Response(
        content=v6_services.json_bytes(payload),
        media_type="application/json; charset=utf-8",
        headers={"Content-Disposition": f'attachment; filename="{equipment_id}_predictive_report.json"'},
    )
