from nicegui import ui
import db
import auth as auth_module
import layout

_INTERVAL_OPTIONS = {
    60: "1 minute",
    300: "5 minutes",
    900: "15 minutes",
    1800: "30 minutes",
    3600: "1 hour",
}


def _fmt_interval(seconds: int) -> str:
    return _INTERVAL_OPTIONS.get(seconds, f"{seconds} s")


def _fmt_ts(ts) -> str:
    return ts.strftime("%Y-%m-%d %H:%M") if ts else "—"


@ui.page("/alarms")
async def alarms_page():
    await auth_module.sync_session()
    if not auth_module.is_authenticated():
        ui.navigate.to("/login")
        return

    user = auth_module.current_user()
    superadmin = auth_module.is_superadmin()

    # Departments the user may manage alarms for
    if superadmin:
        departments = await db.fetch(
            """
            SELECT d.id, d.name AS dept_name, i.name AS org_name, d.organization_id
            FROM department d
            JOIN organization i ON i.id = d.organization_id
            ORDER BY i.name, d.name
            """
        )
    else:
        departments = await db.fetch(
            """
            SELECT d.id, d.name AS dept_name, i.name AS org_name, d.organization_id
            FROM user_department ud
            JOIN department d ON d.id = ud.department_id
            JOIN organization  i ON i.id = d.organization_id
            WHERE ud.user_id = $1::uuid
            ORDER BY i.name, d.name
            """,
            user["id"],
        )

    dept_ids = [str(d["id"]) for d in departments]

    async def load_rules():
        if not dept_ids:
            return []
        placeholders = ", ".join(f"${i + 1}::uuid" for i in range(len(dept_ids)))
        return await db.fetch(
            f"""
            SELECT
                ar.id, ar.name, ar.entity, ar.metric, ar.operator, ar.threshold,
                ar.check_interval, ar.last_checked_value, ar.enabled, ar.department_id,
                d.name  AS dept_name,
                i.name  AS org_name,
                ae.status      AS last_status,
                ae_trig.occurred_at AS last_triggered_at
            FROM alarm_rule ar
            JOIN department d ON d.id = ar.department_id
            JOIN organization  i ON i.id = ar.organization_id
            LEFT JOIN LATERAL (
                SELECT status
                FROM alarm_event
                WHERE rule_id = ar.id
                ORDER BY occurred_at DESC
                LIMIT 1
            ) ae ON TRUE
            LEFT JOIN LATERAL (
                SELECT occurred_at
                FROM alarm_event
                WHERE rule_id = ar.id AND status = 'triggered'
                ORDER BY occurred_at DESC
                LIMIT 1
            ) ae_trig ON TRUE
            WHERE ar.department_id IN ({placeholders})
            ORDER BY i.name, d.name, ar.name
            """,
            *dept_ids,
        )

    rules = await load_rules()

    with layout.page("Alarms"):
        with ui.row().classes("w-full items-center justify-between -mt-2"):
            count_label = ui.label(f"{len(rules)} rule(s)").classes(
                "text-sm text-gray-500"
            )
            ui.button(
                "Add rule",
                icon="add_alert",
                on_click=lambda: _open_add_dialog(departments, refresh),
            ).props("color=primary")

        # ── rules table ───────────────────────────────────────────────────────
        columns = [
            {"name": "name", "label": "Name", "field": "name", "align": "left"},
            {
                "name": "department",
                "label": "Department",
                "field": "department",
                "align": "left",
            },
            {"name": "entity", "label": "Entity", "field": "entity", "align": "left"},
            {
                "name": "condition",
                "label": "Condition",
                "field": "condition",
                "align": "left",
            },
            {
                "name": "last_value",
                "label": "Value",
                "field": "last_value",
                "align": "right",
            },
            {
                "name": "interval",
                "label": "Interval",
                "field": "interval",
                "align": "left",
            },
            {
                "name": "last_triggered",
                "label": "Last triggered",
                "field": "last_triggered",
                "align": "left",
            },
            {"name": "status", "label": "Status", "field": "status", "align": "center"},
            {
                "name": "enabled",
                "label": "Enabled",
                "field": "enabled",
                "align": "center",
            },
            {"name": "actions", "label": "", "field": "actions", "align": "right"},
        ]

        def make_row_data(r):
            return {
                "id": str(r["id"]),
                "name": r["name"],
                "department": f"{r['org_name']} / {r['dept_name']}",
                "entity": r["entity"],
                "metric": r["metric"],
                "condition": f"{r['metric']} {r['operator']} {r['threshold']}",
                "last_value": round(r["last_checked_value"], 4)
                if r["last_checked_value"] is not None
                else "—",
                "interval": _fmt_interval(r["check_interval"]),
                "check_interval": r["check_interval"],
                "last_triggered": _fmt_ts(r["last_triggered_at"]),
                "status": r["last_status"] or "ok",
                "enabled": r["enabled"],
                "department_id": str(r["department_id"]),
                "operator": r["operator"],
                "threshold": r["threshold"],
            }

        row_data = [make_row_data(r) for r in rules]

        with ui.element("div").classes("w-full relative"):
            ui.element("div").classes(
                "absolute right-0 top-0 bottom-0 w-10 pointer-events-none z-10"
            ).style(
                "background: linear-gradient(to left, rgba(255,255,255,0.95), transparent)"
            )
            table = (
                ui.table(columns=columns, rows=row_data, row_key="id")
                .classes("w-full")
                .props("flat")
            )

        # Status badge slot
        table.add_slot(
            "body-cell-status",
            """
            <q-td :props="props">
                <q-badge
                    :color="props.row.status === 'triggered' ? 'negative' : 'positive'"
                    :label="props.row.status === 'triggered' ? 'TRIGGERED' : 'OK'"
                />
            </q-td>
        """,
        )

        # Enabled toggle slot
        table.add_slot(
            "body-cell-enabled",
            """
            <q-td :props="props">
                <q-toggle
                    :model-value="props.row.enabled"
                    @update:model-value="$parent.$emit('toggleenabled', props.row)"
                    dense
                />
            </q-td>
        """,
        )

        # Actions slot
        table.add_slot(
            "body-cell-actions",
            """
            <q-td :props="props">
                <q-btn flat round dense icon="edit" color="grey"
                    @click="$parent.$emit('edit', props.row)" title="Edit" />
                <q-btn flat round dense icon="people" color="primary"
                    @click="$parent.$emit('recipients', props.row)" title="Recipients" />
                <q-btn flat round dense icon="history" color="grey"
                    @click="$parent.$emit('history', props.row)" title="History" />
                <q-btn flat round dense icon="delete" color="negative"
                    @click="$parent.$emit('delete', props.row)" title="Delete" />
            </q-td>
        """,
        )

        async def on_toggle_enabled(e):
            row = e.args
            new_val = not row["enabled"]
            await db.execute(
                """
                UPDATE alarm_rule
                SET enabled         = $1,
                    last_checked_at = CASE WHEN $1 THEN NULL ELSE last_checked_at END
                WHERE id = $2::uuid
                """,
                new_val,
                row["id"],
            )
            for r in table.rows:
                if r["id"] == row["id"]:
                    r["enabled"] = new_val
                    break
            table.update()

        async def on_edit(e):
            await _open_edit_dialog(e.args, departments, refresh)

        async def on_recipients(e):
            await _recipients_dialog(
                e.args["id"], e.args["name"], departments, e.args["department_id"]
            )

        async def on_history(e):
            await _history_dialog(e.args["id"], e.args["name"])

        def on_delete(e):
            _confirm_delete(e.args["id"], e.args["name"], refresh)

        table.on("toggleenabled", on_toggle_enabled)
        table.on("edit", on_edit)
        table.on("recipients", on_recipients)
        table.on("history", on_history)
        table.on("delete", on_delete)

        async def refresh():
            updated = await load_rules()
            table.rows = [make_row_data(r) for r in updated]
            count_label.text = f"{len(updated)} rule(s)"
            table.update()


def _open_add_dialog(departments, on_save_callback):
    dept_options = {
        str(d["id"]): f"{d['org_name']} / {d['dept_name']}" for d in departments
    }
    dept_meta = {str(d["id"]): d for d in departments}

    with ui.dialog() as dlg, ui.card().classes("w-96 p-6 gap-4"):
        ui.label("Add alarm rule").classes("text-lg font-semibold")

        name_input = ui.input("Rule name").classes("w-full")
        dept_select = ui.select(dept_options, label="Department").classes("w-full")
        entity_select = ui.select({}, label="Entity").classes("w-full")
        metric_select = ui.select({}, label="Metric").classes("w-full")
        op_select = ui.select(
            {"<": "<", ">": ">", "<=": "<=", ">=": ">=", "=": "="},
            label="Operator",
        ).classes("w-full")
        threshold_input = ui.number("Threshold", format="%.4f").classes("w-full")
        interval_select = ui.select(
            {k: v for k, v in _INTERVAL_OPTIONS.items()},
            label="Check interval",
            value=300,
        ).classes("w-full")
        error = ui.label("").classes("text-red-500 text-sm")

        async def on_dept_change():
            dept_id = dept_select.value
            if not dept_id:
                return
            entities = await db.fetch(
                "SELECT DISTINCT entity FROM sensor_data WHERE department_id = $1::uuid ORDER BY entity",
                dept_id,
            )
            entity_select.options = {e["entity"]: e["entity"] for e in entities}
            entity_select.value = None
            entity_select.update()
            metric_select.options = {}
            metric_select.value = None
            metric_select.update()

        async def on_entity_change():
            dept_id = dept_select.value
            entity = entity_select.value
            if not dept_id or not entity:
                return
            metrics = await db.fetch(
                """
                SELECT DISTINCT metric FROM sensor_data
                WHERE department_id = $1::uuid AND entity = $2
                ORDER BY metric
                """,
                dept_id,
                entity,
            )
            metric_select.options = {m["metric"]: m["metric"] for m in metrics}
            metric_select.value = None
            metric_select.update()

        dept_select.on("update:model-value", lambda _: on_dept_change())
        entity_select.on("update:model-value", lambda _: on_entity_change())

        async def save():
            error.text = ""
            if not all(
                [
                    name_input.value,
                    dept_select.value,
                    entity_select.value,
                    metric_select.value,
                    op_select.value,
                    threshold_input.value is not None,
                ]
            ):
                error.text = "All fields are required."
                return
            dept = dept_meta.get(dept_select.value)
            if not dept:
                error.text = "Invalid department."
                return
            user = auth_module.current_user()
            try:
                await db.execute(
                    """
                    INSERT INTO alarm_rule
                        (name, department_id, organization_id, entity, metric,
                         operator, threshold, check_interval, created_by)
                    VALUES ($1, $2::uuid, $3::uuid, $4, $5, $6, $7, $8, $9::uuid)
                    """,
                    name_input.value.strip(),
                    dept_select.value,
                    str(dept["organization_id"]),
                    entity_select.value,
                    metric_select.value,
                    op_select.value,
                    float(threshold_input.value),
                    int(interval_select.value),
                    user["id"],
                )
                dlg.close()
                await on_save_callback()
                ui.notify("Alarm rule created.", color="positive")
            except Exception as e:
                error.text = f"Error: {e}"

        with ui.row().classes("w-full justify-end gap-2 mt-2"):
            ui.button("Cancel", on_click=dlg.close).props("flat")
            ui.button("Save", on_click=save).props("color=primary")

    dlg.open()


async def _open_edit_dialog(rule: dict, departments: list, on_save_callback) -> None:
    dept_meta = {str(d["id"]): d for d in departments}

    with ui.dialog() as dlg, ui.card().classes("w-96 p-6 gap-4"):
        ui.label("Edit alarm rule").classes("text-lg font-semibold")

        name_input = ui.input("Rule name", value=rule["name"]).classes("w-full")
        dept_select = ui.select(
            {str(d["id"]): f"{d['org_name']} / {d['dept_name']}" for d in departments},
            label="Department",
            value=rule["department_id"],
        ).classes("w-full")
        entity_select = ui.select({}, label="Entity").classes("w-full")
        metric_select = ui.select({}, label="Metric").classes("w-full")
        op_select = ui.select(
            {"<": "<", ">": ">", "<=": "<=", ">=": ">=", "=": "="},
            label="Operator",
            value=rule["operator"],
        ).classes("w-full")
        threshold_input = ui.number(
            "Threshold", value=rule["threshold"], format="%.4f"
        ).classes("w-full")
        interval_select = ui.select(
            {k: v for k, v in _INTERVAL_OPTIONS.items()},
            label="Check interval",
            value=rule["check_interval"],
        ).classes("w-full")
        error = ui.label("").classes("text-red-500 text-sm")

        # Pre-load entities and metrics for the current department/entity
        entities = await db.fetch(
            "SELECT DISTINCT entity FROM sensor_data WHERE department_id = $1::uuid ORDER BY entity",
            rule["department_id"],
        )
        entity_select.options = {e["entity"]: e["entity"] for e in entities}
        entity_select.value = rule["entity"]
        entity_select.update()

        metrics = await db.fetch(
            """
            SELECT DISTINCT metric FROM sensor_data
            WHERE department_id = $1::uuid AND entity = $2
            ORDER BY metric
            """,
            rule["department_id"],
            rule["entity"],
        )
        metric_select.options = {m["metric"]: m["metric"] for m in metrics}
        metric_select.value = rule["metric"]
        metric_select.update()

        async def on_dept_change():
            dept_id = dept_select.value
            if not dept_id:
                return
            ents = await db.fetch(
                "SELECT DISTINCT entity FROM sensor_data WHERE department_id = $1::uuid ORDER BY entity",
                dept_id,
            )
            entity_select.options = {e["entity"]: e["entity"] for e in ents}
            entity_select.value = None
            entity_select.update()
            metric_select.options = {}
            metric_select.value = None
            metric_select.update()

        async def on_entity_change():
            dept_id = dept_select.value
            entity = entity_select.value
            if not dept_id or not entity:
                return
            mets = await db.fetch(
                """
                SELECT DISTINCT metric FROM sensor_data
                WHERE department_id = $1::uuid AND entity = $2
                ORDER BY metric
                """,
                dept_id,
                entity,
            )
            metric_select.options = {m["metric"]: m["metric"] for m in mets}
            metric_select.value = None
            metric_select.update()

        dept_select.on("update:model-value", lambda _: on_dept_change())
        entity_select.on("update:model-value", lambda _: on_entity_change())

        async def save():
            error.text = ""
            if not all(
                [
                    name_input.value,
                    dept_select.value,
                    entity_select.value,
                    metric_select.value,
                    op_select.value,
                    threshold_input.value is not None,
                ]
            ):
                error.text = "All fields are required."
                return
            dept = dept_meta.get(dept_select.value)
            if not dept:
                error.text = "Invalid department."
                return
            try:
                await db.execute(
                    """
                    UPDATE alarm_rule
                    SET name           = $1,
                        department_id  = $2::uuid,
                        organization_id   = $3::uuid,
                        entity         = $4,
                        metric         = $5,
                        operator       = $6,
                        threshold      = $7,
                        check_interval = $8
                    WHERE id = $9::uuid
                    """,
                    name_input.value.strip(),
                    dept_select.value,
                    str(dept["organization_id"]),
                    entity_select.value,
                    metric_select.value,
                    op_select.value,
                    float(threshold_input.value),
                    int(interval_select.value),
                    rule["id"],
                )
                dlg.close()
                await on_save_callback()
                ui.notify("Alarm rule updated.", color="positive")
            except Exception as e:
                error.text = f"Error: {e}"

        with ui.row().classes("w-full justify-end gap-2 mt-2"):
            ui.button("Cancel", on_click=dlg.close).props("flat")
            ui.button("Save", on_click=save).props("color=primary")

    dlg.open()


async def _recipients_dialog(
    rule_id: str, rule_name: str, departments: list, department_id: str
) -> None:
    with ui.dialog() as dlg, ui.card().classes("w-96 p-6 gap-4"):
        ui.label(f"Recipients — {rule_name}").classes("text-lg font-semibold")

        ui.label("Current recipients").classes("text-sm font-medium text-gray-600")
        recipient_list = ui.column().classes("w-full gap-1")

        async def refresh():
            recipient_list.clear()
            current = await db.fetch(
                """
                SELECT ua.id, ua.name, ua.email
                FROM alarm_recipient ar
                JOIN user_account ua ON ua.id = ar.user_id
                WHERE ar.rule_id = $1::uuid
                ORDER BY ua.name
                """,
                rule_id,
            )
            with recipient_list:
                if not current:
                    ui.label("No recipients.").classes("text-sm text-gray-400 italic")
                for rec in current:
                    with ui.row().classes("w-full items-center justify-between py-1"):
                        ui.label(f"{rec['name']} ({rec['email']})").classes("text-sm")
                        uid = str(rec["id"])
                        ui.button(icon="close").props(
                            "flat round dense color=negative"
                        ).on("click", handler=lambda _, u=uid: remove_recipient(u))

        async def remove_recipient(user_id: str):
            await db.execute(
                "DELETE FROM alarm_recipient WHERE rule_id = $1::uuid AND user_id = $2::uuid",
                rule_id,
                user_id,
            )
            await refresh()

        ui.separator()
        ui.label("Add recipient").classes("text-sm font-medium text-gray-600")

        members = await db.fetch(
            """
            SELECT ua.id, ua.name, ua.email
            FROM user_account ua
            WHERE ua.is_superadmin = TRUE
               OR EXISTS (
                   SELECT 1 FROM user_department ud
                   WHERE ud.user_id = ua.id AND ud.department_id = $1::uuid
               )
            ORDER BY ua.name
            """,
            department_id,
        )
        member_options = {str(m["id"]): f"{m['name']} ({m['email']})" for m in members}
        add_select = ui.select(member_options, label="User").classes("w-full")

        async def add_recipient():
            if not add_select.value:
                return
            try:
                await db.execute(
                    """
                    INSERT INTO alarm_recipient (rule_id, user_id)
                    VALUES ($1::uuid, $2::uuid)
                    ON CONFLICT DO NOTHING
                    """,
                    rule_id,
                    add_select.value,
                )
                add_select.value = None
                await refresh()
            except Exception as e:
                ui.notify(f"Error: {e}", color="negative")

        with ui.row().classes("w-full justify-end gap-2 mt-1"):
            ui.button("Add", icon="add", on_click=add_recipient).props("color=primary")

        await refresh()

        ui.separator()
        with ui.row().classes("w-full justify-end"):
            ui.button("Close", on_click=dlg.close).props("flat")

    dlg.open()


async def _history_dialog(rule_id: str, rule_name: str) -> None:
    with ui.dialog() as dlg, ui.card().classes("w-[560px] p-6 gap-4"):
        ui.label(f"History — {rule_name}").classes("text-lg font-semibold")

        events = await db.fetch(
            """
            SELECT occurred_at, status, value
            FROM alarm_event
            WHERE rule_id = $1::uuid
            ORDER BY occurred_at DESC
            LIMIT 50
            """,
            rule_id,
        )

        columns = [
            {
                "name": "occurred_at",
                "label": "Time",
                "field": "occurred_at",
                "align": "left",
            },
            {"name": "status", "label": "Status", "field": "status", "align": "center"},
            {"name": "value", "label": "Value", "field": "value", "align": "right"},
        ]
        row_data = [
            {
                "occurred_at": r["occurred_at"].strftime("%Y-%m-%d %H:%M:%S"),
                "status": r["status"],
                "value": round(r["value"], 4) if r["value"] is not None else "—",
            }
            for r in events
        ]

        hist_table = ui.table(
            columns=columns, rows=row_data, row_key="occurred_at"
        ).classes("w-full")
        hist_table.add_slot(
            "body-cell-status",
            """
            <q-td :props="props">
                <q-badge
                    :color="props.row.status === 'triggered' ? 'negative' : 'positive'"
                    :label="props.row.status.toUpperCase()"
                />
            </q-td>
        """,
        )

        if not events:
            ui.label("No events yet.").classes("text-sm text-gray-400 italic")

        ui.separator()
        with ui.row().classes("w-full justify-end"):
            ui.button("Close", on_click=dlg.close).props("flat")

    dlg.open()


def _confirm_delete(rule_id: str, rule_name: str, on_delete_callback) -> None:
    with ui.dialog() as dlg, ui.card().classes("p-6 gap-4"):
        ui.label(f'Delete rule "{rule_name}"?').classes("text-lg font-semibold")
        ui.label(
            "All events and recipients for this rule will also be deleted."
        ).classes("text-sm text-gray-500")

        async def confirm():
            try:
                await db.execute("DELETE FROM alarm_rule WHERE id = $1::uuid", rule_id)
                dlg.close()
                await on_delete_callback()
                ui.notify("Alarm rule deleted.", color="positive")
            except Exception as e:
                ui.notify(f"Error: {e}", color="negative")

        with ui.row().classes("w-full justify-end gap-2"):
            ui.button("Cancel", on_click=dlg.close).props("flat")
            ui.button("Delete", on_click=confirm).props("color=negative")

    dlg.open()
