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


def _row_label(r: dict) -> str:
    parts = [r["organisation_name"], r["department_name"] or "All departments"]
    if r["entity"] and r["entity"] != "Full access":
        parts.append(r["entity"])
    return " / ".join(parts)


@ui.page("/admin/access-management")
async def access_management_page():
    await auth_module.sync_session()
    if not auth_module.is_authenticated():
        ui.navigate.to("/login")
        return

    is_superadmin = auth_module.is_superadmin()
    admin_dept_ids: list = []

    if not is_superadmin:
        user = auth_module.current_user()
        admin_dept_ids = await auth_module.get_admin_department_ids(user["id"])
        if not admin_dept_ids:
            ui.navigate.to("/dashboard")
            return

    if is_superadmin:
        rows = await db.fetch(
            """
            SELECT p.id, p.entity,
                   p.time_start, p.time_end, p.created_at,
                   i.name  AS organisation_name,
                   d.name  AS department_name,
                   COUNT(up.user_id) AS n_members
            FROM management p
            JOIN organisation i       ON i.id = p.organisation_id
            LEFT JOIN department d ON d.id = p.department_id
            LEFT JOIN user_management up ON up.management_id = p.id
            GROUP BY p.id, i.name, d.name
            ORDER BY p.created_at DESC
            """
        )
        organisations = await db.fetch(
            "SELECT id, name FROM organisation ORDER BY name"
        )
        departments = await db.fetch(
            "SELECT id, organisation_id, name FROM department ORDER BY name"
        )
    else:
        rows = await db.fetch(
            """
            SELECT p.id, p.entity,
                   p.time_start, p.time_end, p.created_at,
                   i.name  AS organisation_name,
                   d.name  AS department_name,
                   COUNT(up.user_id) AS n_members
            FROM management p
            JOIN organisation i       ON i.id = p.organisation_id
            LEFT JOIN department d ON d.id = p.department_id
            LEFT JOIN user_management up ON up.management_id = p.id
            WHERE p.department_id = ANY($1::uuid[])
            GROUP BY p.id, i.name, d.name
            ORDER BY p.created_at DESC
            """,
            admin_dept_ids,
        )
        organisations = await db.fetch(
            """
            SELECT DISTINCT i.id, i.name
            FROM department d
            JOIN organisation i ON i.id = d.organisation_id
            WHERE d.id = ANY($1::uuid[])
            ORDER BY i.name
            """,
            admin_dept_ids,
        )
        departments = await db.fetch(
            "SELECT id, organisation_id, name FROM department WHERE id = ANY($1::uuid[]) ORDER BY name",
            admin_dept_ids,
        )

    users = await db.fetch("SELECT id, name, email FROM user_account ORDER BY name")

    with layout.page("Access Management"):
        with ui.row().classes("w-full items-center justify-between -mt-2"):
            ui.label(f"{len(rows)} access grant(s)").classes("text-sm text-gray-500")
            ui.button(
                "New access grant", icon="add", on_click=lambda: add_dialog.open()
            ).props("color=primary")

        columns = [
            {"name": "entity", "label": "Entity", "field": "entity", "align": "left"},
            {
                "name": "time_start",
                "label": "From",
                "field": "time_start",
                "align": "left",
            },
            {"name": "time_end", "label": "To", "field": "time_end", "align": "left"},
            {
                "name": "n_members",
                "label": "Members",
                "field": "n_members",
                "align": "center",
            },
            {"name": "actions", "label": "", "field": "actions", "align": "right"},
        ]

        def _make_row(r):
            return {
                "id": str(r["id"]),
                "label": _row_label(
                    {
                        "organisation_name": r["organisation_name"],
                        "department_name": r["department_name"],
                        "entity": r["entity"],
                    }
                ),
                "group": f"{r['organisation_name']} / {r['department_name'] or 'All departments'}",
                "organisation_name": r["organisation_name"],
                "department_name": r["department_name"] or "Full access",
                "entity": r["entity"] or "Full access",
                "time_start": r["time_start"].strftime("%Y-%m-%d")
                if r["time_start"]
                else "Full access",
                "time_end": r["time_end"].strftime("%Y-%m-%d")
                if r["time_end"]
                else "Full access",
                "n_members": r["n_members"],
            }

        def _add_separators(data):
            result = []
            current_group = None
            for row in data:
                if row["group"] != current_group:
                    current_group = row["group"]
                    result.append(
                        {
                            "_sep": True,
                            "group": current_group,
                            "id": f"_sep_{current_group}",
                        }
                    )
                result.append(row)
            return result

        row_data = _add_separators([_make_row(r) for r in rows])

        table = (
            ui.table(columns=columns, rows=row_data, row_key="id")
            .classes("w-full")
            .props("flat")
        )
        table.add_slot(
            "body",
            """
            <template v-if="props.row._sep">
                <tr>
                    <td colspan="5" class="q-pa-sm bg-grey-2">
                        <span class="text-weight-bold text-grey-8 text-sm">{{ props.row.group }}</span>
                    </td>
                </tr>
            </template>
            <q-tr v-else :props="props">
                <q-td key="entity" :props="props">{{ props.row.entity }}</q-td>
                <q-td key="time_start" :props="props">{{ props.row.time_start }}</q-td>
                <q-td key="time_end" :props="props">{{ props.row.time_end }}</q-td>
                <q-td key="n_members" :props="props" class="text-center">{{ props.row.n_members }}</q-td>
                <q-td key="actions" :props="props" class="text-right">
                    <q-btn flat round dense icon="group_add" color="blue"
                        @click="$parent.$emit('addmember', props.row)" />
                    <q-btn flat round dense icon="delete" color="negative"
                        @click="$parent.$emit('delete', props.row)" />
                </q-td>
            </q-tr>
        """,
        )
        table.on(
            "addmember", lambda e: _members_dialog(e.args["id"], e.args["label"], users)
        )
        table.on(
            "delete",
            lambda e: _confirm_delete(
                e.args["id"],
                e.args["label"],
                admin_dept_ids if not is_superadmin else None,
            ),
        )

        # ── add access grant dialog ─────────────────────────────────────────
        inst_options = {str(i["id"]): i["name"] for i in organisations}
        all_depts = departments  # kept for reactive filtering

        with ui.dialog() as add_dialog, ui.card().classes("w-96 p-6 gap-3"):
            ui.label("New access grant").classes("text-lg font-semibold")

            inst_select = ui.select(inst_options, label="Organisation").classes(
                "w-full"
            )

            dept_options_default = (
                {} if not is_superadmin else {"": "— All departments"}
            )
            dept_select = ui.select(
                dept_options_default,
                label="Department" if not is_superadmin else "Department (optional)",
            ).classes("w-full")

            entity_select = ui.select(
                {"": "— All entities"},
                label="Entity (optional)",
                value="",
            ).classes("w-full")

            ui.label("Time window (optional — leave blank for no restriction)").classes(
                "text-xs text-gray-500"
            )
            with ui.row().classes("w-full gap-2"):
                date_input = ui.input("Date range").classes("w-full")
                date_input.visible = False
                ui.date().props("range").bind_value(
                    date_input,
                    forward=lambda x: f"{x['from']} - {x['to']}" if x else None,
                    backward=lambda x: (
                        {
                            "from": x.split(" - ")[0],
                            "to": x.split(" - ")[1],
                        }
                        if " - " in (x or "")
                        else None
                    ),
                )

            error = ui.label("").classes("text-red-500 text-sm")

            def on_organisation_change():
                if is_superadmin:
                    opts = {"": "— All departments"} | {
                        str(d["id"]): d["name"]
                        for d in all_depts
                        if str(d["organisation_id"]) == str(inst_select.value)
                    }
                else:
                    opts = {
                        str(d["id"]): d["name"]
                        for d in all_depts
                        if str(d["organisation_id"]) == str(inst_select.value)
                    }
                dept_select.options = opts
                dept_select.value = "" if is_superadmin else None
                dept_select.update()
                entity_select.options = {"": "— All entities"}
                entity_select.value = ""
                entity_select.update()

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

            inst_select.on("update:model-value", lambda e: on_organisation_change())
            dept_select.on("update:model-value", lambda e: on_department_change())

            async def save_management():
                error.text = ""
                if not inst_select.value:
                    error.text = "Organisation is required."
                    return
                if not is_superadmin and not dept_select.value:
                    error.text = "Department is required."
                    return
                if not is_superadmin and dept_select.value not in admin_dept_ids:
                    error.text = "You can only create grants for your departments."
                    return
                try:
                    dept_id = dept_select.value or None
                    entity_val = entity_select.value or None
                    from datetime import date as _date

                    dr = date_input.value or ""
                    if " - " in dr:
                        parts = [p.strip() for p in dr.split(" - ", 1)]
                        t_start = _date.fromisoformat(parts[0]) if parts[0] else None
                        t_end = _date.fromisoformat(parts[1]) if parts[1] else None
                    else:
                        t_start = t_end = None
                    await db.execute(
                        """
                        INSERT INTO management
                            (organisation_id, department_id, entity, time_start, time_end)
                        VALUES
                            ($1::uuid, $2::uuid, $3, $4, $5)
                        """,
                        inst_select.value,
                        dept_id,
                        entity_val,
                        t_start,
                        t_end,
                    )
                    add_dialog.close()
                    ui.navigate.to("/admin/access-management")
                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=add_dialog.close).props("flat")
                ui.button("Save", on_click=save_management).props("color=primary")


async def _members_dialog(management_id: str, label: str, users: list) -> None:
    existing = await db.fetch(
        "SELECT user_id FROM user_management WHERE management_id = $1::uuid",
        management_id,
    )
    existing_ids = {str(r["user_id"]) for r in existing}
    all_user_options = {str(u["id"]): f"{u['name']} ({u['email']})" for u in users}

    with ui.dialog() as dlg, ui.card().classes("w-96 p-6 gap-4"):
        ui.label(f"Members — {label}").classes("text-lg font-semibold")

        user_select = ui.select(
            {k: v for k, v in all_user_options.items() if k not in existing_ids},
            label="Add user",
        ).classes("w-full")
        role_select = ui.select(
            {"viewer": "Viewer", "admin": "Admin"}, label="Role", value="viewer"
        ).classes("w-full")

        async def grant():
            if not user_select.value:
                ui.notify("Select a user.", color="warning")
                return
            try:
                await db.execute(
                    """
                    INSERT INTO user_management (user_id, management_id, role)
                    VALUES ($1::uuid, $2::uuid, $3)
                    ON CONFLICT (user_id, management_id) DO UPDATE SET role = EXCLUDED.role
                    """,
                    user_select.value,
                    management_id,
                    role_select.value,
                )
                ui.notify("Member added.", color="positive")
                existing_ids.add(user_select.value)
                user_select.options = {
                    k: v for k, v in all_user_options.items() if k not in existing_ids
                }
                user_select.value = None
                user_select.update()
                await _refresh_members(management_id, members_table)
            except Exception as e:
                ui.notify(f"Error: {e}", color="negative")

        with ui.row().classes("w-full gap-2"):
            ui.button("Add member", icon="person_add", on_click=grant).props(
                "color=primary"
            )

        ui.separator()
        ui.label("Current members").classes("text-sm font-medium text-gray-600")
        members_table = ui.table(
            columns=[
                {"name": "name", "label": "Name", "field": "name", "align": "left"},
                {"name": "role", "label": "Role", "field": "role", "align": "left"},
                {"name": "rm", "label": "", "field": "rm", "align": "right"},
            ],
            rows=[],
            row_key="user_id",
        ).classes("w-full")
        members_table.add_slot(
            "body-cell-rm",
            """
            <q-td :props="props">
                <q-btn flat round dense icon="person_remove" color="negative"
                    @click="$parent.$emit('remove', props.row)" />
            </q-td>
        """,
        )
        members_table.on(
            "remove",
            lambda e: _remove_member(
                e.args["user_id"],
                management_id,
                members_table,
                existing_ids,
                all_user_options,
                user_select,
            ),
        )

        await _refresh_members(management_id, members_table)
        ui.button("Close", on_click=dlg.close).props("flat").classes("self-end")

    dlg.open()


async def _refresh_members(management_id: str, table: ui.table) -> None:
    members = await db.fetch(
        """
        SELECT up.user_id, ua.name, up.role
        FROM user_management up
        JOIN user_account ua ON ua.id = up.user_id
        WHERE up.management_id = $1::uuid
        ORDER BY ua.name
        """,
        management_id,
    )
    table.rows = [
        {"user_id": str(m["user_id"]), "name": m["name"], "role": m["role"]}
        for m in members
    ]
    table.update()


async def _remove_member(
    user_id: str,
    management_id: str,
    table: ui.table,
    existing_ids: set,
    all_user_options: dict,
    user_select: ui.select,
) -> None:
    await db.execute(
        "DELETE FROM user_management WHERE user_id = $1::uuid AND management_id = $2::uuid",
        user_id,
        management_id,
    )
    existing_ids.discard(user_id)
    user_select.options = {
        k: v for k, v in all_user_options.items() if k not in existing_ids
    }
    user_select.update()
    await _refresh_members(management_id, table)


def _confirm_delete(
    management_id: str, label: str, allowed_dept_ids: list | None = None
) -> None:
    with ui.dialog() as dlg, ui.card().classes("p-6 gap-4"):
        ui.label(f'Delete "{label}"?').classes("text-lg font-semibold")
        ui.label("All member access grants for this entry will be removed.").classes(
            "text-sm text-gray-500"
        )

        async def confirm():
            try:
                if allowed_dept_ids is not None:
                    row = await db.fetchrow(
                        "SELECT department_id FROM management WHERE id = $1",
                        management_id,
                    )
                    if not row or str(row["department_id"]) not in allowed_dept_ids:
                        ui.notify("Permission denied.", color="negative")
                        dlg.close()
                        return
                await db.execute("DELETE FROM management WHERE id = $1", management_id)
                dlg.close()
                ui.navigate.to("/admin/access-management")
            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()
