from datetime import datetime, timedelta

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


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

    user = auth_module.current_user()
    managements = await _accessible_departments(user)
    management_options = {
        str(p["department_id"]): f"{p['organization_name']} :: {p['department_name']}"
        for p in managements
    }

    with layout.page("Data Explorer"):
        if not management_options:
            with ui.card().classes("w-full p-6 flex items-center gap-4"):
                ui.icon("folder_off", size="xl").classes("text-gray-300")
                with ui.column().classes("gap-1"):
                    ui.label("No department access").classes(
                        "text-base font-semibold text-gray-600"
                    )
                    ui.label(
                        "You have not been assigned to any department yet. "
                        "Ask your administrator to grant you access."
                    ).classes("text-sm text-gray-400")
            return

        # ── filters ──────────────────────────────────────────────────────────
        with ui.card().classes("w-full p-4"):
            ui.label("Filters").classes("text-sm font-semibold text-gray-600 mb-2")
            with ui.row().classes("w-full gap-4 flex-wrap"):
                dept_select = ui.select(management_options, label="Department").classes(
                    "flex-1 min-w-40"
                )
                entity_select = ui.select({"": "— all —"}, label="Entity").classes(
                    "flex-1 min-w-40"
                )
                metric_select = ui.select({}, label="Metric").classes("flex-1 min-w-40")

            with ui.row().classes("w-full gap-4 flex-wrap items-end"):
                date_input = ui.date_input(
                    "Range", placeholder="YYYY-MM-DD to YYYY-MM-DD", range_input=True
                )
                limit_input = ui.number(
                    "Max rows", value=500, min=1, max=10000
                ).classes("w-32")

                with ui.row().classes("gap-2"):
                    search_btn = ui.button("Search", icon="search").props(
                        "color=primary"
                    )
                    export_btn = ui.button("Export CSV", icon="download").props(
                        "flat color=primary"
                    )
                    export_all_btn = ui.button("Export All", icon="download").props(
                        "flat color=secondary"
                    )

        # ── results ──────────────────────────────────────────────────────────
        status = ui.label("").classes("text-sm text-gray-400")
        columns = [
            {
                "name": "time",
                "label": "Time",
                "field": "time",
                "align": "left",
                "sortable": True,
            },
            {
                "name": "entity",
                "label": "Entity",
                "field": "entity",
                "align": "left",
                "sortable": True,
            },
            {
                "name": "metric",
                "label": "Metric",
                "field": "metric",
                "align": "left",
                "sortable": True,
            },
            {
                "name": "value",
                "label": "Value",
                "field": "value",
                "align": "right",
                "sortable": True,
            },
            {"name": "tags", "label": "Tags", "field": "tags", "align": "left"},
        ]
        result_table = (
            ui.table(columns=columns, rows=[], row_key="time")
            .classes("w-full")
            .props("flat")
        )

        last_rows: list[dict] = []

        async def on_department_change(dept_id):
            if not dept_id:
                return
            entities = await db.fetch(
                "SELECT DISTINCT entity FROM sensor_data_for_department_per_uuid($1, $2::uuid) ORDER BY entity",
                user["email"],
                dept_id,
            )
            entity_select.options = {"": "— all —"} | {
                e["entity"]: e["entity"] for e in entities
            }
            entity_select.value = ""
            entity_select.update()

            metrics = await db.fetch(
                "SELECT DISTINCT metric FROM sensor_data_for_department_per_uuid($1, $2::uuid) ORDER BY metric",
                user["email"],
                dept_id,
            )
            metric_select.options = {"": "— all —"} | {
                m["metric"]: m["metric"] for m in metrics
            }
            metric_select.value = ""
            metric_select.update()

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

        async def on_entity_change(entity):
            if not entity or not dept_select.value:
                return
            metrics = await db.fetch(
                "SELECT DISTINCT metric FROM sensor_data_for_department_per_uuid($1, $2::uuid) WHERE entity = $3 ORDER BY metric",
                user["email"],
                dept_select.value,
                entity,
            )
            metric_select.options = {"": "— all —"} | {
                m["metric"]: m["metric"] for m in metrics
            }
            metric_select.value = ""
            metric_select.update()

        entity_select.on(
            "update:model-value", lambda e: on_entity_change(entity_select.value)
        )

        async def do_search():
            nonlocal last_rows
            if not dept_select.value:
                ui.notify("Select a department.", color="warning")
                return

            params: list = [user["email"], dept_select.value]
            clauses = []
            i = 3
            if entity_select.value:
                clauses.append(f"entity = ${i}")
                params.append(entity_select.value)
                i += 1
            if metric_select.value:
                clauses.append(f"metric = ${i}")
                params.append(metric_select.value)
                i += 1
            if date_input.value.strip():
                clauses.append(f"time >= ${i}::timestamptz")
                dt = datetime.strptime(
                    date_input.value.strip().split(" - ")[0], "%Y-%m-%d"
                )
                params.append(dt)
                i += 1
                clauses.append(f"time < ${i}::timestamptz")
                dt = datetime.strptime(
                    date_input.value.strip().split(" - ")[1], "%Y-%m-%d"
                )
                params.append(dt + timedelta(days=1))
                i += 1

            where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
            limit = int(limit_input.value or 500)

            rows = await db.fetch(
                f"""
                SELECT time, entity, metric, value, tags
                FROM sensor_data_for_department_per_uuid($1, $2::uuid)
                {where}
                ORDER BY time DESC
                LIMIT {limit}
                """,
                *params,
            )
            last_rows = [
                {
                    "time": r["time"].strftime("%Y-%m-%d %H:%M:%S"),
                    "entity": r["entity"],
                    "metric": r["metric"],
                    "value": round(r["value"], 6),
                    "tags": str(r["tags"]) if r["tags"] else "",
                }
                for r in rows
            ]
            result_table.rows = last_rows
            result_table.update()
            status.text = f"{len(last_rows)} row(s) returned."

        def do_export():
            if not last_rows:
                ui.notify("Run a search first.", color="warning")
                return
            buf = io.StringIO()
            writer = csv.DictWriter(
                buf, fieldnames=["time", "entity", "metric", "value", "tags"]
            )
            writer.writeheader()
            writer.writerows(last_rows)
            ui.download(buf.getvalue().encode(), "sensor_data.csv")

        async def do_export_all():
            if not dept_select.value:
                ui.notify("Select a department.", color="warning")
                return

            params: list = [user["email"], dept_select.value]
            clauses = []
            i = 3
            if entity_select.value:
                clauses.append(f"entity = ${i}")
                params.append(entity_select.value)
                i += 1
            if metric_select.value:
                clauses.append(f"metric = ${i}")
                params.append(metric_select.value)
                i += 1
            if date_input.value.strip():
                clauses.append(f"time >= ${i}::timestamptz")
                dt = datetime.strptime(
                    date_input.value.strip().split(" - ")[0], "%Y-%m-%d"
                )
                params.append(dt)
                i += 1
                clauses.append(f"time < ${i}::timestamptz")
                dt = datetime.strptime(
                    date_input.value.strip().split(" - ")[1], "%Y-%m-%d"
                )
                params.append(dt + timedelta(days=1))
                i += 1

            where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
            rows = await db.fetch(
                f"""
                SELECT time, entity, metric, value, tags
                FROM sensor_data_for_department_per_uuid($1, $2::uuid)
                {where}
                ORDER BY time DESC
                """,
                *params,
            )
            all_rows = [
                {
                    "time": r["time"].strftime("%Y-%m-%d %H:%M:%S"),
                    "entity": r["entity"],
                    "metric": r["metric"],
                    "value": round(r["value"], 6),
                    "tags": str(r["tags"]) if r["tags"] else "",
                }
                for r in rows
            ]
            buf = io.StringIO()
            writer = csv.DictWriter(
                buf, fieldnames=["time", "entity", "metric", "value", "tags"]
            )
            writer.writeheader()
            writer.writerows(all_rows)
            ui.download(buf.getvalue().encode(), "sensor_data_all.csv")
            ui.notify(f"Exported {len(all_rows)} row(s).", color="positive")

        search_btn.on("click", do_search)
        export_btn.on("click", do_export)
        export_all_btn.on("click", do_export_all)


async def _accessible_departments(user: dict) -> list:
    return await db.fetch(
        """
        SELECT DISTINCT ON (m.department_id)
            m.department_id,
            d.name  AS department_name,
            i.name  AS organization_name
        FROM user_management um
        JOIN management     m  ON m.id  = um.management_id
        JOIN department     d  ON d.id  = m.department_id
        JOIN organization      i  ON i.id  = m.organization_id
        JOIN user_account   ua ON ua.id = um.user_id
        WHERE ua.email = $1
          AND m.department_id IS NOT NULL
        ORDER BY m.department_id, i.name, d.name
        """,
        user["email"],
    )
