import os
import json
import subprocess
import sys
import threading
import unittest
import urllib.request
from http.server import HTTPServer
from unittest.mock import patch

from companion import attention_hook
import companion.server as server


class AttentionHookTests(unittest.TestCase):
    def test_build_payload_uses_standard_hook_fields(self):
        payload = attention_hook.build_attention_payload(
            "Codex",
            {
                "hook_event_name": "PermissionRequest",
                "session_id": "session-123",
                "cwd": "/Users/jameslopez/projects/CodexBar-ESP32",
            },
        )

        self.assertEqual(payload["provider"], "Codex")
        self.assertEqual(payload["event"], "PermissionRequest")
        self.assertEqual(payload["session_id"], "session-123")
        self.assertEqual(payload["cwd"], "/Users/jameslopez/projects/CodexBar-ESP32")

    def test_build_payload_supports_camel_case_fallbacks(self):
        payload = attention_hook.build_attention_payload(
            "Claude",
            {
                "hookEventName": "SessionStart",
                "sessionId": "session-abc",
                "cwd": "/tmp/project",
                "lastAssistantMessage": "Done.",
            },
        )

        self.assertEqual(payload["provider"], "Claude")
        self.assertEqual(payload["event"], "SessionStart")
        self.assertEqual(payload["session_id"], "session-abc")
        self.assertEqual(payload["cwd"], "/tmp/project")
        self.assertEqual(payload["last_assistant_message"], "Done.")

    def test_build_payload_uses_process_cwd_when_missing(self):
        with patch.object(os, "getcwd", return_value="/tmp/fallback"):
            payload = attention_hook.build_attention_payload("Codex", {"event": "Stop"})

        self.assertEqual(payload["event"], "Stop")
        self.assertEqual(payload["cwd"], "/tmp/fallback")

    def test_build_payload_forwards_message_field(self):
        payload = attention_hook.build_attention_payload(
            "Codex",
            {"event": "Notification", "cwd": "/tmp/p", "message": "Automatic approval review approved: Bash(ls)"},
        )
        self.assertEqual(payload["message"], "Automatic approval review approved: Bash(ls)")

    def test_build_payload_omits_message_when_absent(self):
        payload = attention_hook.build_attention_payload(
            "Codex",
            {"event": "Notification", "cwd": "/tmp/p"},
        )
        self.assertNotIn("message", payload)


class AttentionHookIntegrationTests(unittest.TestCase):
    def setUp(self):
        server.NTFY_TOPIC = ""
        with server._attention_lock:
            server._attention_records.clear()
            server._attention_seq = 0
        server._invalidate_usage_cache()
        self.httpd = HTTPServer(("127.0.0.1", 0), server.Handler)
        self.port = self.httpd.server_address[1]
        self.thread = threading.Thread(target=self.httpd.serve_forever, daemon=True)
        self.thread.start()

    def tearDown(self):
        self.httpd.shutdown()
        self.httpd.server_close()
        self.thread.join(timeout=2)

    def test_hook_script_posts_to_companion_endpoint(self):
        hook_input = json.dumps({
            "hook_event_name": "PermissionRequest",
            "session_id": "subprocess-session",
            "cwd": "/Users/jameslopez/projects/CodexBar-ESP32",
        })
        env = os.environ.copy()
        env["CODEXBAR_ATTENTION_URL"] = f"http://127.0.0.1:{self.port}/attention"

        result = subprocess.run(
            [sys.executable, "companion/attention_hook.py", "Codex"],
            input=hook_input,
            text=True,
            capture_output=True,
            timeout=5,
            env=env,
        )

        self.assertEqual(result.returncode, 0)
        with urllib.request.urlopen(f"http://127.0.0.1:{self.port}/status", timeout=5) as resp:
            body = json.loads(resp.read())
        codex = body["providers"][1]["attention"]
        self.assertEqual(codex["state"], "needs_user")
        self.assertEqual(codex["primary_project"], "CodexBar-ESP32")

    def test_codex_notify_wrapper_posts_notification_attention(self):
        hook_input = json.dumps({
            "session_id": "notify-session",
            "cwd": "/Users/jameslopez/projects/CodexBar-ESP32",
        })
        env = os.environ.copy()
        env["CODEXBAR_ATTENTION_URL"] = f"http://127.0.0.1:{self.port}/attention"

        result = subprocess.run(
            [sys.executable, "companion/codex_notify.py"],
            input=hook_input,
            text=True,
            capture_output=True,
            timeout=5,
            env=env,
        )

        self.assertEqual(result.returncode, 0, result.stderr)
        with urllib.request.urlopen(f"http://127.0.0.1:{self.port}/status", timeout=5) as resp:
            self.assertEqual(resp.status, 200)
            body = json.loads(resp.read())
        codex = body["providers"][1]["attention"]
        self.assertEqual(codex["state"], "needs_user")
        self.assertEqual(codex["reason"], "notification")


    def test_lmstudio_notify_posts_completion_attention(self):
        hook_input = json.dumps({
            "session_id": "lmstudio-session",
            "cwd": "/Users/jameslopez/projects/CodexBar-ESP32",
        })
        env = os.environ.copy()
        env["CODEXBAR_ATTENTION_URL"] = f"http://127.0.0.1:{self.port}/attention"

        result = subprocess.run(
            [sys.executable, "companion/lmstudio_notify.py"],
            input=hook_input,
            text=True,
            capture_output=True,
            timeout=5,
            env=env,
        )

        self.assertEqual(result.returncode, 0, result.stderr)
        with urllib.request.urlopen(f"http://127.0.0.1:{self.port}/status", timeout=5) as resp:
            body = json.loads(resp.read())
        self.assertEqual([provider["name"] for provider in body["providers"]], ["Claude", "Codex"])


if __name__ == "__main__":
    unittest.main()
