# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

"""Sensor telemetry view for sensor-type contacts."""

import logging

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, GLib, Gtk
from meshy.models import Contact, ContactType
from meshy.utils import format_number

log = logging.getLogger(__name__)

_REQUEST_TIMEOUT_MS = 15000
_AUTO_REFRESH_MS = 30000


@Gtk.Template(resource_path="/page/codeberg/sesivany/Meshy/ui/sensor-view.ui")
class SensorView(Gtk.Box):
    __gtype_name__ = "MeshySensorView"

    _placeholder = Gtk.Template.Child("placeholder")
    _scroll = Gtk.Template.Child("scroll")
    _content_box = Gtk.Template.Child("content_box")
    _status_group = Gtk.Template.Child("status_group")
    _status_row = Gtk.Template.Child("status_row")
    _map_frame = Gtk.Template.Child("map_frame")
    _action_bar = Gtk.Template.Child("action_bar")
    _spinner = Gtk.Template.Child("spinner")
    _refresh_btn = Gtk.Template.Child("refresh_btn")
    _login_btn = Gtk.Template.Child("login_btn")

    def __init__(self, window, **kwargs):
        super().__init__(**kwargs)
        self._window = window
        self._contact: Contact | None = None
        self._timeout_id: int | None = None
        self._auto_refresh_id: int | None = None
        self._map_widget = None
        self._dynamic_groups: list[Gtk.Widget] = []

        self._refresh_btn.connect("clicked", lambda *_: self._request_telemetry())
        self._login_btn.connect("clicked", self._on_login_clicked)

    def set_sensor(self, contact: Contact):
        self.clear()
        self._contact = contact
        self._placeholder.set_visible(False)
        self._scroll.set_visible(True)
        self._action_bar.set_visible(True)
        self._status_row.set_subtitle(_("Requesting..."))
        self._status_group.set_visible(True)
        self._login_btn.set_visible(True)
        self._request_telemetry()
        self._auto_refresh_id = GLib.timeout_add(_AUTO_REFRESH_MS, self._on_auto_refresh)

    def clear(self):
        self._cancel_timeout()
        self._cancel_auto_refresh()
        self._cleanup_map()
        self._remove_dynamic_groups()
        self._contact = None
        self._status_row.set_subtitle("--")
        self._status_group.set_visible(True)
        self._placeholder.set_visible(True)
        self._scroll.set_visible(False)
        self._action_bar.set_visible(False)
        self._spinner.set_visible(False)
        self._spinner.set_spinning(False)
        self._login_btn.set_visible(False)

    def _request_telemetry(self):
        if not self._contact:
            return
        self._cancel_timeout()
        self._spinner.set_visible(True)
        self._spinner.set_spinning(True)
        self._refresh_btn.set_sensitive(False)
        self._timeout_id = GLib.timeout_add(_REQUEST_TIMEOUT_MS, self._on_timeout)
        self._window.get_telemetry(
            self._contact.public_key,
            self._on_telemetry_response,
            contact_type=ContactType.SENSOR,
        )

    def _on_telemetry_response(self, lpp_data):
        self._cancel_timeout()
        self._spinner.set_visible(False)
        self._spinner.set_spinning(False)
        self._refresh_btn.set_sensitive(True)

        if not lpp_data:
            self._status_row.set_subtitle(_("No telemetry data"))
            return

        from meshy.protocol import parse_cayenne_lpp

        entries = parse_cayenne_lpp(lpp_data)
        if not entries:
            self._status_row.set_subtitle(_("No telemetry data"))
            return

        self._display_telemetry(entries)

    def _display_telemetry(self, entries: list[dict]):
        from meshy.models import battery_percent_from_mv
        from meshy.protocol import LPP_GPS, LPP_VOLTAGE

        self._remove_dynamic_groups()
        self._cleanup_map()
        self._status_group.set_visible(False)

        channels: dict[int, list[dict]] = {}
        for entry in entries:
            channels.setdefault(entry["channel"], []).append(entry)

        gps_entry = None
        for ch in sorted(channels):
            group = Adw.PreferencesGroup(title=_("Channel {}").format(ch))

            for entry in channels[ch]:
                val = entry["value"]
                if isinstance(val, dict):
                    if entry["type"] == LPP_GPS:
                        gps_entry = entry
                        alt_str = (
                            f", alt {format_number(val['alt'], 0)} m"
                            if abs(val.get("alt", 0)) > 0.01
                            else ""
                        )
                        subtitle = f"{val['lat']:.4f}, {val['lon']:.4f}{alt_str}"
                    else:
                        subtitle = ", ".join(f"{k}: {v}" for k, v in val.items())
                elif entry["type"] == LPP_VOLTAGE and ch == 1:
                    pct = battery_percent_from_mv(int(val * 1000))
                    subtitle = f"{pct}% ({format_number(val, 2)} V)"
                elif isinstance(val, float):
                    subtitle = f"{format_number(val, 2)} {entry['unit']}"
                else:
                    subtitle = f"{val} {entry['unit']}"

                title = _("Battery") if entry["type"] == LPP_VOLTAGE and ch == 1 else entry["name"]
                row = Adw.ActionRow(title=title, subtitle=subtitle)
                group.add(row)

            insert_pos = len(self._dynamic_groups) + 1
            self._content_box.insert_child_after(
                group, self._status_group if insert_pos == 1 else self._dynamic_groups[-1]
            )
            self._dynamic_groups.append(group)

        if gps_entry:
            self._show_map(gps_entry)

    def _show_map(self, gps_entry: dict):
        try:
            from meshy.views import create_shumate_map
            from meshy.views.map_view import _MARKER_COLORS, _MARKER_ICONS, _SELF_COLOR, MapView

            lat = gps_entry["value"]["lat"]
            lon = gps_entry["value"]["lon"]

            map_widget, marker_layer, viewport = create_shumate_map(zoom_level=14, lat=lat, lon=lon)
            map_widget.set_vexpand(False)
            map_widget.set_size_request(-1, 250)

            r, g, b = _MARKER_COLORS.get(self._contact.type, (0.5, 0.5, 0.5))
            icon_name = _MARKER_ICONS.get(self._contact.type, "avatar-default-symbolic")
            marker = MapView.create_marker(r, g, b, icon_name, self._contact.name, lat, lon)
            marker_layer.add_marker(marker)

            lats, lons = [lat], [lon]
            si = self._window.self_info
            self_lat = si.get("adv_lat", 0.0)
            self_lon = si.get("adv_lon", 0.0)
            if abs(self_lat) > 1e-6 or abs(self_lon) > 1e-6:
                self_name = si.get("name", "") or _("My Device")
                self_marker = MapView.create_marker(
                    *_SELF_COLOR, "avatar-default-symbolic", self_name, self_lat, self_lon
                )
                marker_layer.add_marker(self_marker)
                lats.append(self_lat)
                lons.append(self_lon)

            MapView.fit_viewport(viewport, lats, lons)
            self._map_frame.set_child(map_widget)
            self._map_frame.set_visible(True)
            self._map_widget = map_widget
        except Exception as e:
            log.error("Failed to create map for sensor telemetry: %s", e, exc_info=True)
            lat = gps_entry["value"]["lat"]
            lon = gps_entry["value"]["lon"]
            from meshy.platforms import is_macos

            label = Gtk.Label()
            if is_macos():
                label.set_markup(f"<b>GPS Coordinates:</b>\nLat: {lat:.6f}, Lon: {lon:.6f}")
            else:
                label.set_markup(f"<b>GPS Coordinates:</b>\n📍 {lat:.6f}, {lon:.6f}")
            self._map_frame.set_child(label)
            self._map_frame.set_visible(True)

    def _on_login_clicked(self, btn):
        if not self._contact:
            return

        builder = Gtk.Builder.new_from_resource(
            "/page/codeberg/sesivany/Meshy/ui/sensor-login-dialog.ui"
        )
        dialog = builder.get_object("dialog")
        pwd_entry = builder.get_object("pwd_entry")
        login_btn = builder.get_object("login_btn")
        status_label = builder.get_object("status_label")

        dialog.set_title(_("Login to {}").format(self._contact.name))

        contact = self._contact

        def _on_login(*_args):
            password = pwd_entry.get_text().strip()
            if not password:
                return
            login_btn.set_sensitive(False)
            status_label.set_label(_("Logging in..."))

            def _on_login_result(success, login_timestamp, is_admin, acl_perms=3):
                if success:
                    dialog.close()
                    self._window.mark_room_logged_in(contact.public_key_hex, acl_perms)
                    self._window.show_toast(_("Logged in to {}").format(contact.name))
                else:
                    login_btn.set_sensitive(True)
                    status_label.set_label(_("Login failed"))

            self._window.send_login(contact.public_key, password, _on_login_result)

        login_btn.connect("clicked", _on_login)
        pwd_entry.connect("apply", _on_login)
        dialog.present(self._window)

    def _on_timeout(self):
        self._timeout_id = None
        self._spinner.set_visible(False)
        self._spinner.set_spinning(False)
        self._refresh_btn.set_sensitive(True)
        if not self._dynamic_groups:
            self._status_row.set_subtitle(_("No response (timed out)"))
        return False

    def _on_auto_refresh(self):
        if self._contact:
            self._request_telemetry()
            return True
        return False

    def _cancel_timeout(self):
        if self._timeout_id is not None:
            GLib.source_remove(self._timeout_id)
            self._timeout_id = None

    def _cancel_auto_refresh(self):
        if self._auto_refresh_id is not None:
            GLib.source_remove(self._auto_refresh_id)
            self._auto_refresh_id = None

    def _cleanup_map(self):
        if self._map_widget:
            from meshy.views import deregister_map_widget

            deregister_map_widget(self._map_widget)
            self._map_widget = None
        self._map_frame.set_child(None)
        self._map_frame.set_visible(False)

    def _remove_dynamic_groups(self):
        for group in self._dynamic_groups:
            self._content_box.remove(group)
        self._dynamic_groups.clear()
