from datetime import datetime, timedelta

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


BUCKETS = {
    "Raw": None,
    "Minutely": "1 minute",
    "Hourly": "1 hour",
    "Daily": "1 day",
    "Weekly": "1 week",
}


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

    user = auth_module.current_user()
    departments = await _accessible_departments(user)
    dept_options = {
        str(d["department_id"]): f"{d['organization_name']} :: {d['department_name']}"
        for d in departments
    }

    with layout.page("Plots"):
        if not dept_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(dept_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
                )
                bucket_select = ui.select(
                    list(BUCKETS.keys()), label="Aggregation", value="Minutely"
                ).classes("w-36")
                plot_btn = ui.button("Plot", icon="show_chart").props("color=primary")

        # ── chart ─────────────────────────────────────────────────────────────
        status = ui.label("").classes("text-sm text-gray-400")
        _PLOT_CONFIG = {"displaylogo": False}
        plot = ui.plotly({"data": [], "layout": {}, "config": _PLOT_CONFIG}).classes(
            "w-full"
        )

        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_plot():
            if not dept_select.value:
                ui.notify("Select a department.", color="warning")
                return
            if not metric_select.value and not entity_select.value:
                ui.notify("Select at least a metric or an entity.", color="warning")
                return

            params: list = [user["email"], dept_select.value]
            clauses = []
            i = 3
            if metric_select.value:
                clauses.append(f"metric = ${i}")
                params.append(metric_select.value)
                i += 1
            if entity_select.value:
                clauses.append(f"entity = ${i}")
                params.append(entity_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 ""
            bucket = BUCKETS[bucket_select.value]

            if bucket:
                sql = f"""
                    SELECT time_bucket(INTERVAL '{bucket}', time) AS bucket,
                           entity, metric, AVG(value) AS value
                    FROM sensor_data_for_department_per_uuid($1, $2::uuid)
                    {where}
                    GROUP BY bucket, entity, metric
                    ORDER BY bucket
                """
            else:
                sql = f"""
                    SELECT time AS bucket, entity, metric, value
                    FROM sensor_data_for_department_per_uuid($1, $2::uuid)
                    {where}
                    ORDER BY time
                    LIMIT 5000
                """
            rows = await db.fetch(sql, *params)

            if not rows:
                status.text = "No data for the selected filters."
                plot.figure = {"data": [], "layout": {}, "config": _PLOT_CONFIG}
                plot.update()
                return

            traces: dict[str, tuple[list, list]] = {}
            for r in rows:
                key = f"{r['entity']} — {r['metric']}"
                if key not in traces:
                    traces[key] = ([], [])
                traces[key][0].append(r["bucket"])
                traces[key][1].append(r["value"])

            agg_label = bucket_select.value
            title = f"{metric_select.value or 'data'} — {agg_label} aggregation"
            plot.figure = {
                "data": [
                    {
                        "type": "scatter",
                        "name": label,
                        "x": times,
                        "y": values,
                        "mode": "lines+markers",
                        "marker": {"size": 4},
                    }
                    for label, (times, values) in traces.items()
                ],
                "layout": {
                    "title": title,
                    "xaxis": {"title": "Time"},
                    "yaxis": {"title": metric_select.value or "value"},
                    "legend": {"title": {"text": "Entity — Metric"}},
                    "hovermode": "x unified",
                    "margin": {"l": 40, "r": 20, "t": 50, "b": 40},
                    "height": 500,
                },
                "config": _PLOT_CONFIG,
            }
            plot.update()
            status.text = f"{len(rows)} data point(s) plotted."

        plot_btn.on("click", do_plot)


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"],
    )
