import logging
import os
from nicegui import ui
import db
import auth as auth_module
import layout
import mail


@ui.page("/admin/users")
async def users_page():
    await auth_module.sync_session()
    if not auth_module.is_authenticated():
        ui.navigate.to("/login")
        return
    if not auth_module.is_superadmin():
        ui.navigate.to("/dashboard")
        return

    rows = await db.fetch(
        """
        SELECT u.id, u.email, u.name, u.is_superadmin, u.created_at,
               COALESCE(STRING_AGG(DISTINCT i.name || ' / ' || d.name, chr(10) ORDER BY i.name || ' / ' || d.name), '—') AS departments,
               COUNT(DISTINCT up.management_id) AS n_management
        FROM user_account u
        LEFT JOIN user_department ud ON ud.user_id = u.id
        LEFT JOIN department d       ON d.id = ud.department_id
        LEFT JOIN organization  i       ON i.id = d.organization_id
        LEFT JOIN user_management up    ON up.user_id = u.id
        GROUP BY u.id
        ORDER BY u.name
        """
    )
    organizations = await db.fetch("SELECT id, name FROM organization ORDER BY name")

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

        # ── user table ───────────────────────────────────────────────────────
        columns = [
            {"name": "name", "label": "Name", "field": "name", "align": "left"},
            {"name": "email", "label": "Email", "field": "email", "align": "left"},
            {
                "name": "departments",
                "label": "Departments",
                "field": "departments",
                "align": "left",
            },
            {
                "name": "n_management",
                "label": "Access grants",
                "field": "n_management",
                "align": "center",
            },
            {"name": "actions", "label": "", "field": "actions", "align": "right"},
        ]
        row_data = [
            {
                "id": str(r["id"]),
                "name": r["name"],
                "email": r["email"],
                "departments": r["departments"],
                "n_management": r["n_management"],
                "superadmin": "✓" if r["is_superadmin"] else "",
            }
            for r in rows
        ]

        table = ui.table(columns=columns, rows=row_data, row_key="id").classes("w-full")
        with table.add_slot("body-cell-departments"):
            with table.cell():
                ui.element("div").props(':textContent="props.row.departments"').style(
                    "white-space: pre-line; line-height: 1.6"
                )
        table.add_slot(
            "body-cell-actions",
            """
            <q-td :props="props">
                <q-btn flat round dense icon="key" color="orange"
                    @click="$parent.$emit('resetpw', props.row)" />
                <q-btn flat round dense icon="folder_shared" color="primary"
                    @click="$parent.$emit('access', props.row)" />
                <q-btn flat round dense
                    :icon="props.row.superadmin ? 'shield' : 'o_shield'"
                    :color="props.row.superadmin ? 'primary' : 'grey'"
                    @click="$parent.$emit('togglesuperadmin', props.row)" />
                <q-btn flat round dense icon="delete" color="negative"
                    @click="$parent.$emit('delete', props.row)" />
            </q-td>
        """,
        )
        table.on(
            "resetpw", lambda e: _reset_password_dialog(e.args["id"], e.args["name"])
        )
        table.on("delete", lambda e: _confirm_delete(e.args["id"], e.args["name"]))

        async def on_access(e):
            await _access_dialog(e.args["id"], e.args["name"], organizations)

        async def on_toggle_superadmin(e):
            await _toggle_superadmin(
                e.args["id"], e.args["name"], bool(e.args["superadmin"]), table
            )

        table.on("access", on_access)
        table.on("togglesuperadmin", on_toggle_superadmin)

        # ── add user dialog ──────────────────────────────────────────────────
        with ui.dialog() as add_dialog, ui.card().classes("w-80 p-6 gap-4"):
            ui.label("New user").classes("text-lg font-semibold")
            name_input = ui.input("Full name").classes("w-full")
            email_input = ui.input("Email").classes("w-full").props("type=email")
            pw_input = (
                ui.input("Initial password", password=True, password_toggle_button=True)
                .on("keydown.enter", lambda e: save_user())
                .classes("w-full")
            )
            sadmin_cb = ui.checkbox("Superadmin")
            error = ui.label("").classes("text-red-500 text-sm")

            async def save_user():
                error.text = ""
                if not all([name_input.value, email_input.value, pw_input.value]):
                    error.text = "All fields are required."
                    return
                if len(pw_input.value) < 8:
                    error.text = "Password must be at least 8 characters."
                    return
                try:
                    hashed = await auth_module.hash_password(pw_input.value)
                    new_id = await db.fetchval(
                        """
                        INSERT INTO user_account (email, name, password_hash, is_superadmin)
                        VALUES ($1, $2, $3, $4)
                        RETURNING id
                        """,
                        email_input.value.lower().strip(),
                        name_input.value.strip(),
                        hashed,
                        sadmin_cb.value,
                    )
                    add_dialog.close()
                    try:
                        portal_url = os.environ.get(
                            "PORTAL_URL", "http://localhost:8081"
                        )
                        await mail.send_email(
                            to=email_input.value.lower().strip(),
                            subject="Welcome to LEAF Portal",
                            body=(
                                f"Hi {name_input.value.strip()},\n\n"
                                f"Your LEAF Portal account has been created.\n\n"
                                f"Email: {email_input.value.lower().strip()}\n\n"
                                f"Please use the link below to set your password:\n"
                                f"{portal_url}/forgot-password\n"
                            ),
                        )
                    except Exception:
                        logging.exception("Welcome email failed")
                    await _access_dialog(
                        str(new_id), name_input.value.strip(), organizations
                    )
                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_user).props("color=primary")


def _reset_password_dialog(user_id: str, name: str) -> None:
    with ui.dialog() as dlg, ui.card().classes("w-80 p-6 gap-4"):
        ui.label(f"Set password for {name}").classes("text-lg font-semibold")
        pw1 = ui.input(
            "New password", password=True, password_toggle_button=True
        ).classes("w-full")
        pw2 = ui.input("Confirm", password=True, password_toggle_button=True).classes(
            "w-full"
        )
        error = ui.label("").classes("text-red-500 text-sm")

        async def save():
            error.text = ""
            if pw1.value != pw2.value:
                error.text = "Passwords do not match."
                return
            if len(pw1.value) < 8:
                error.text = "Password must be at least 8 characters."
                return
            await auth_module.set_password(user_id, pw1.value)
            ui.notify("Password updated.", color="positive")
            dlg.close()

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

    dlg.open()


async def _access_dialog(user_id: str, name: str, organizations: list) -> None:
    with ui.dialog() as dlg, ui.card().classes("w-96 p-6 gap-4"):
        ui.label(f"Department access — {name}").classes("text-lg font-semibold")

        # ── current assignments ──────────────────────────────────────────────
        ui.label("Current departments").classes("text-sm font-medium text-gray-600")
        current_list = ui.column().classes("w-full gap-1")

        async def refresh():
            current_list.clear()
            assigned = await db.fetch(
                """
                SELECT ud.department_id, d.name AS dept_name, i.name AS inst_name, ud.role
                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,
            )
            with current_list:
                if not assigned:
                    ui.label("No departments assigned.").classes(
                        "text-sm text-gray-400 italic"
                    )
                for a in assigned:
                    with ui.row().classes("w-full items-center justify-between py-1"):
                        ui.label(f"{a['inst_name']} / {a['dept_name']}").classes(
                            "text-sm"
                        )
                        ui.badge(a["role"]).props("color=primary")
                        ui.button(icon="close").props(
                            "flat round dense color=negative"
                        ).on(
                            "click",
                            handler=lambda _, did=str(a["department_id"]): revoke(did),
                        )

        async def revoke(dept_id: str):
            # Remove organisational tracking entry
            await db.execute(
                "DELETE FROM user_department WHERE user_id = $1::uuid AND department_id = $2::uuid",
                user_id,
                dept_id,
            )
            # Remove the corresponding full-scope access management entry (entity=NULL, time=NULL)
            await db.execute(
                """
                DELETE FROM user_management
                WHERE user_id = $1::uuid
                  AND management_id IN (
                      SELECT p.id FROM management p
                      WHERE p.department_id = $2::uuid
                        AND p.entity       IS NULL
                        AND p.time_start   IS NULL
                        AND p.time_end     IS NULL
                  )
                """,
                user_id,
                dept_id,
            )
            await refresh()

        # ── add new assignment ───────────────────────────────────────────────
        ui.separator()
        ui.label("Add department").classes("text-sm font-medium text-gray-600")

        organization_options = {str(i["id"]): i["name"] for i in organizations}
        inst_select = ui.select(organization_options, label="Organization").classes(
            "w-full"
        )
        dept_select = (
            ui.select({}, label="Departments", multiple=True)
            .classes("w-full")
            .props("use-chips")
        )
        role_select = ui.select(
            {"member": "Member", "admin": "Admin"}, label="Role", value="member"
        ).classes("w-full")
        error = ui.label("").classes("text-red-500 text-sm")

        async def on_inst_change(inst_id):
            if not inst_id:
                return
            depts = await db.fetch(
                "SELECT id, name FROM department WHERE organization_id = $1::uuid ORDER BY name",
                inst_id,
            )
            dept_select.options = {str(d["id"]): d["name"] for d in depts}
            dept_select.value = []
            dept_select.update()

        inst_select.on(
            "update:model-value", lambda e: on_inst_change(inst_select.value)
        )

        async def grant():
            error.text = ""
            selected = dept_select.value or []
            if not selected:
                error.text = "Select at least one department."
                return
            try:
                for dept_id in selected:
                    # Organisational tracking
                    await db.execute(
                        """
                        INSERT INTO user_department (user_id, department_id, role)
                        VALUES ($1::uuid, $2::uuid, $3)
                        ON CONFLICT (user_id, department_id) DO UPDATE SET role = EXCLUDED.role
                        """,
                        user_id,
                        dept_id,
                        role_select.value,
                    )
                    # Full-scope access management entry (entity=NULL, time=NULL = no restrictions)
                    dept_info = await db.fetchrow(
                        "SELECT d.name, d.organization_id FROM department d WHERE d.id = $1::uuid",
                        dept_id,
                    )
                    management_id = await db.fetchval(
                        """
                        INSERT INTO management (organization_id, department_id, name)
                        VALUES ($1::uuid, $2::uuid, $3)
                        RETURNING id
                        """,
                        str(dept_info["organization_id"]),
                        dept_id,
                        dept_info["name"],
                    )
                    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 NOTHING
                        """,
                        user_id,
                        str(management_id),
                        role_select.value,
                    )
                inst_select.value = None
                dept_select.options = {}
                dept_select.value = []
                dept_select.update()
                await refresh()
            except Exception as e:
                error.text = f"Error: {e}"

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

        await refresh()

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

    dlg.open()


async def _toggle_superadmin(
    user_id: str, name: str, currently_superadmin: bool, table: ui.table
) -> None:
    new_value = not currently_superadmin
    await db.execute(
        "UPDATE user_account SET is_superadmin = $1 WHERE id = $2",
        new_value,
        user_id,
    )
    action = "granted" if new_value else "revoked"
    ui.notify(f"Superadmin {action} for {name}.", color="positive")

    for row in table.rows:
        if row["id"] == user_id:
            row["superadmin"] = new_value
            break
    table.update()


def _confirm_delete(user_id: str, name: str) -> None:
    with ui.dialog() as dlg, ui.card().classes("p-6 gap-4"):
        ui.label(f'Delete user "{name}"?').classes("text-lg font-semibold")
        ui.label(
            "This will also remove all their department and access grant memberships."
        ).classes("text-sm text-gray-500")

        async def confirm():
            try:
                await db.execute("DELETE FROM user_account WHERE id = $1", user_id)
                dlg.close()
                ui.navigate.to("/admin/users")
            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()
