"""
personality_node.py — ROS2 personality system for saltybot.

Overview
--------
Loads a SOUL.md persona file, maintains per-person relationship memory in
SQLite, computes mood, and publishes personality state.  All tunable params
support ROS2 dynamic reconfigure (``ros2 param set``).

Subscriptions
-------------
  /social/person_detected  (std_msgs/String)
      JSON payload: ``{"person_id": "alice", "event_type": "greeting",
                       "delta_score": 1.0}``
      event_type defaults to "detection" if absent.
      delta_score defaults to 0.0 if absent.

Publications
------------
  /social/personality/state  (saltybot_social_msgs/PersonalityState)
      Published at ``publish_rate`` Hz.

Services
--------
  /social/personality/query_mood  (saltybot_social_msgs/QueryMood)
      Query mood + greeting for any person_id.

Parameters (dynamic reconfigure via ros2 param set)
-------------------
  soul_file        (str)   Path to SOUL.md persona file.
  db_path          (str)   SQLite database file path.
  reload_interval  (float) How often to poll SOUL.md for changes (s).
  publish_rate     (float) State publication rate (Hz).

Usage
-----
  ros2 launch saltybot_social_personality personality.launch.py
  ros2 launch saltybot_social_personality personality.launch.py soul_file:=/my/SOUL.md
  ros2 param set /personality_node soul_file /tmp/new_SOUL.md
"""

import json
import os

import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import SetParametersResult
from std_msgs.msg import String, Header

from saltybot_social_msgs.msg import PersonalityState
from saltybot_social_msgs.srv import QueryMood

from .soul_loader import load_soul, SoulWatcher
from .mood_engine import (
    compute_mood, get_relationship_tier, build_greeting,
    EVENT_GREETING, EVENT_POSITIVE, EVENT_NEGATIVE, EVENT_DETECTION,
)
from .relationship_db import RelationshipDB

_DEFAULT_SOUL = os.path.join(
    os.path.dirname(__file__), "..", "config", "SOUL.md"
)
_DEFAULT_DB = os.path.expanduser("~/.ros/saltybot_personality.db")


class PersonalityNode(Node):

    def __init__(self):
        super().__init__("personality_node")

        # ── Parameters ────────────────────────────────────────────────────────
        self.declare_parameter("soul_file",       _DEFAULT_SOUL)
        self.declare_parameter("db_path",         _DEFAULT_DB)
        self.declare_parameter("reload_interval", 5.0)
        self.declare_parameter("publish_rate",    2.0)

        self._p = {}
        self._reload_ros_params()

        # ── State ─────────────────────────────────────────────────────────────
        self._soul           = {}
        self._current_person = ""   # person_id currently being addressed
        self._watcher        = None

        # ── Database ──────────────────────────────────────────────────────────
        self._db = RelationshipDB(self._p["db_path"])

        # ── Load initial SOUL.md ──────────────────────────────────────────────
        self._load_soul_safe()
        self._start_watcher()

        # ── Dynamic reconfigure callback ─────────────────────────────────────
        self.add_on_set_parameters_callback(self._on_params_changed)

        # ── Subscriptions ─────────────────────────────────────────────────────
        self.create_subscription(
            String, "/social/person_detected", self._person_detected_cb, 10
        )

        # ── Publishers ────────────────────────────────────────────────────────
        self._state_pub = self.create_publisher(
            PersonalityState, "/social/personality/state", 10
        )

        # ── Services ──────────────────────────────────────────────────────────
        self.create_service(
            QueryMood,
            "/social/personality/query_mood",
            self._query_mood_cb,
        )

        # ── Timers ────────────────────────────────────────────────────────────
        self._pub_timer = self.create_timer(
            1.0 / self._p["publish_rate"], self._publish_state
        )

        self.get_logger().info(
            f"PersonalityNode ready  "
            f"persona={self._soul.get('name', '?')!r}  "
            f"mood={self._current_mood()!r}  "
            f"db={self._p['db_path']!r}"
        )

    # ── Parameter helpers ──────────────────────────────────────────────────────

    def _reload_ros_params(self):
        self._p = {
            "soul_file":       self.get_parameter("soul_file").value,
            "db_path":         self.get_parameter("db_path").value,
            "reload_interval": self.get_parameter("reload_interval").value,
            "publish_rate":    self.get_parameter("publish_rate").value,
        }

    def _on_params_changed(self, params):
        """Dynamic reconfigure — apply changed params without restarting node."""
        for param in params:
            if param.name == "soul_file":
                # Restart watcher on new soul_file
                self._stop_watcher()
                self._p["soul_file"] = param.value
                self._load_soul_safe()
                self._start_watcher()
                self.get_logger().info(f"soul_file changed → {param.value!r}")
            elif param.name in self._p:
                self._p[param.name] = param.value
                if param.name == "publish_rate" and self._pub_timer:
                    self._pub_timer.cancel()
                    self._pub_timer = self.create_timer(
                        1.0 / max(0.1, param.value), self._publish_state
                    )
        return SetParametersResult(successful=True)

    # ── SOUL.md ────────────────────────────────────────────────────────────────

    def _load_soul_safe(self):
        try:
            path = os.path.realpath(self._p["soul_file"])
            self._soul = load_soul(path)
            self.get_logger().info(
                f"SOUL.md loaded: {self._soul.get('name', '?')!r}  "
                f"humor={self._soul.get('humor_level')}  "
                f"sass={self._soul.get('sass_level')}  "
                f"base_mood={self._soul.get('base_mood')!r}"
            )
        except Exception as exc:
            self.get_logger().error(f"Failed to load SOUL.md: {exc}")
            if not self._soul:
                # Fall back to minimal defaults so the node stays alive
                self._soul = {
                    "name":               "Salty",
                    "humor_level":        5,
                    "sass_level":         3,
                    "base_mood":          "curious",
                    "threshold_regular":  5,
                    "threshold_favorite": 20,
                    "greeting_stranger":  "Hello!",
                    "greeting_regular":   "Hi {name}!",
                    "greeting_favorite":  "Hey {name}!!",
                }

    def _start_watcher(self):
        if not self._soul:
            return
        self._watcher = SoulWatcher(
            path=self._p["soul_file"],
            on_reload=self._on_soul_reloaded,
            interval=self._p["reload_interval"],
            on_error=lambda exc: self.get_logger().warn(
                f"SOUL.md hot-reload error: {exc}"
            ),
        )
        self._watcher.start()

    def _stop_watcher(self):
        if self._watcher:
            self._watcher.stop()
            self._watcher = None

    def _on_soul_reloaded(self, soul: dict):
        self._soul = soul
        self.get_logger().info(
            f"SOUL.md reloaded: persona={soul.get('name')!r}  "
            f"humor={soul.get('humor_level')}  base_mood={soul.get('base_mood')!r}"
        )

    # ── Mood helpers ───────────────────────────────────────────────────────────

    def _current_mood(self) -> str:
        if not self._current_person or not self._soul:
            return self._soul.get("base_mood", "curious") if self._soul else "curious"
        person = self._db.get_person(self._current_person)
        recent = self._db.get_recent_events(self._current_person, window_s=120.0)
        return compute_mood(
            soul              = self._soul,
            score             = person["score"],
            interaction_count = person["interaction_count"],
            recent_events     = recent,
        )

    def _state_for_person(self, person_id: str) -> dict:
        """Build a complete state dict for a given person_id."""
        person = self._db.get_person(person_id) if person_id else {
            "score": 0.0, "interaction_count": 0
        }
        recent = self._db.get_recent_events(person_id, window_s=120.0) if person_id else []

        mood  = compute_mood(
            soul              = self._soul,
            score             = person["score"],
            interaction_count = person["interaction_count"],
            recent_events     = recent,
        )
        tier     = get_relationship_tier(self._soul, person["interaction_count"])
        greeting = build_greeting(self._soul, tier, mood, person_id)

        return {
            "person_id":         person_id,
            "mood":              mood,
            "tier":              tier,
            "score":             person["score"],
            "interaction_count": person["interaction_count"],
            "greeting":          greeting,
        }

    # ── Callbacks ──────────────────────────────────────────────────────────────

    def _person_detected_cb(self, msg: String):
        """Handle incoming person detection / interaction event.

        Expected JSON payload::

            {
                "person_id":   "alice",           # required
                "event_type":  "greeting",         # optional, default "detection"
                "delta_score": 1.0                 # optional, default 0.0
            }
        """
        try:
            data = json.loads(msg.data)
        except json.JSONDecodeError as exc:
            self.get_logger().warn(f"Bad JSON on /social/person_detected: {exc}")
            return

        person_id  = data.get("person_id", "").strip()
        if not person_id:
            self.get_logger().warn("person_detected msg missing 'person_id'")
            return

        event_type  = data.get("event_type",  EVENT_DETECTION)
        delta_score = float(data.get("delta_score", 0.0))

        # Increment score by +1 for detection events automatically
        if event_type == EVENT_DETECTION and delta_score == 0.0:
            delta_score = 0.5

        self._db.record_interaction(
            person_id   = person_id,
            event_type  = event_type,
            details     = {k: v for k, v in data.items()
                           if k not in ("person_id", "event_type", "delta_score")},
            delta_score = delta_score,
        )
        self._current_person = person_id

    def _query_mood_cb(self, request: QueryMood.Request, response: QueryMood.Response):
        """Service handler: return mood + greeting for a specific person."""
        if not self._soul:
            response.success = False
            response.message = "SOUL.md not loaded"
            return response

        person_id = (request.person_id or self._current_person).strip()
        state = self._state_for_person(person_id)

        response.mood               = state["mood"]
        response.relationship_tier  = state["tier"]
        response.relationship_score = float(state["score"])
        response.interaction_count  = int(state["interaction_count"])
        response.greeting_text      = state["greeting"]
        response.success            = True
        response.message            = ""
        return response

    # ── Publish ────────────────────────────────────────────────────────────────

    def _publish_state(self):
        if not self._soul:
            return

        state = self._state_for_person(self._current_person)

        msg = PersonalityState()
        msg.header           = Header()
        msg.header.stamp     = self.get_clock().now().to_msg()
        msg.header.frame_id  = "personality"
        msg.persona_name     = str(self._soul.get("name", "Salty"))
        msg.mood             = state["mood"]
        msg.person_id        = state["person_id"]
        msg.relationship_tier  = state["tier"]
        msg.relationship_score = float(state["score"])
        msg.interaction_count  = int(state["interaction_count"])
        msg.greeting_text    = state["greeting"]

        self._state_pub.publish(msg)

    # ── Lifecycle ──────────────────────────────────────────────────────────────

    def destroy_node(self):
        self._stop_watcher()
        self._db.close()
        super().destroy_node()


# ── Entry point ────────────────────────────────────────────────────────────────

def main(args=None):
    rclpy.init(args=args)
    node = PersonalityNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.try_shutdown()


if __name__ == "__main__":
    main()
