import json
import os
import sys
import threading
import time
import unittest
from http.server import BaseHTTPRequestHandler, HTTPServer

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from stele_sdk import SteleClient


class RecordingHandler(BaseHTTPRequestHandler):
    last_health = None
    last_query = None
    last_protect = None
    last_supersede = None
    last_forget = None
    last_stats = None
    last_bulk_forget_auth = None
    last_export_auth = None

    def log_message(self, format, *args):
        return

    def _read_json(self):
        length = int(self.headers.get("Content-Length", "0") or 0)
        if length == 0:
            return None
        raw = self.rfile.read(length)
        if not raw:
            return None
        return json.loads(raw.decode("utf-8"))

    def _send_json(self, status_code, payload):
        encoded = json.dumps(payload).encode("utf-8")
        self.send_response(status_code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        self.wfile.write(encoded)

    def do_GET(self):
        if self.path == "/memory.health":
            payload = self._read_json()
            RecordingHandler.last_health = payload
            response = {
                "meta": {
                    "api_version": "v1",
                    "request_id": "req-test",
                    "timestamp_ms": int(time.time() * 1000),
                },
                "status": "HEALTH_STATUS_SERVING",
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.stats":
            payload = self._read_json()
            RecordingHandler.last_stats = payload
            meta = (payload or {}).get("meta") or {}
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                },
                "stats": {"total_memories": 0},
            }
            self._send_json(200, response)
            return
        self.send_response(404)
        self.end_headers()

    def do_POST(self):
        payload = self._read_json()
        meta = (payload or {}).get("meta") or {}
        if self.path == "/memory.query":
            RecordingHandler.last_query = payload
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                },
                "memories": [],
                "abstain_reason": "",
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.protect":
            RecordingHandler.last_protect = payload
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                }
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.supersede":
            RecordingHandler.last_supersede = payload
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                }
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.forget":
            RecordingHandler.last_forget = payload
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                },
                "deleted": True,
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.bulk.forget":
            RecordingHandler.last_bulk_forget_auth = self.headers.get("Authorization")
            response = {
                "meta": {
                    "api_version": meta.get("api_version", "v1"),
                    "request_id": meta.get("request_id", "req-test"),
                    "timestamp_ms": int(time.time() * 1000),
                },
                "result": {
                    "timestamp_ms": int(time.time() * 1000),
                    "dry_run": False,
                    "items": [],
                    "summary": {"deleted": 0, "failed": 0},
                },
            }
            self._send_json(200, response)
            return
        if self.path == "/memory.export":
            RecordingHandler.last_export_auth = self.headers.get("Authorization")
            body = (
                '{"kind":"stele_export","schema_version":"v1","exported_at_ms":1,"include_deleted":false,"counts":{"memories":0,"edges":0},"meta":{"api_version":"v1","request_id":"req-test","timestamp_ms":1}}\n'
            )
            encoded = body.encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
            self.send_header("Content-Length", str(len(encoded)))
            self.end_headers()
            self.wfile.write(encoded)
            return
        self.send_response(404)
        self.end_headers()


class StelePythonSmokeTest(unittest.TestCase):
    def test_health_and_query(self):
        server = HTTPServer(("127.0.0.1", 0), RecordingHandler)
        thread = threading.Thread(target=server.serve_forever)
        thread.daemon = True
        thread.start()

        try:
            base_url = f"http://127.0.0.1:{server.server_port}"
            client = SteleClient(base_url=base_url, agent_id="agent-1", session_id="sess-1")
            health = client.health()
            self.assertEqual(health["status"], "HEALTH_STATUS_SERVING")

            query = client.query(
                {
                    "query_text": "hello",
                    "budget_tokens": 10,
                    "scope_order": ["SCOPE_AGENT"],
                }
            )
            self.assertEqual(query["abstain_reason"], "")

            protect = client.protect({"memory_id": "mem-1", "protected_flag": True})
            self.assertIn("meta", protect)

            supersede = client.supersede(
                {
                    "supersedes_id": "mem-1",
                    "superseding_id": "mem-2",
                    "reason": "cleanup",
                }
            )
            self.assertIn("meta", supersede)

            forget = client.forget({"memory_id": "mem-1", "hard_delete": False})
            self.assertTrue(forget.get("deleted"))

            stats = client.stats({"limit": 5, "include_recent": True})
            self.assertIn("stats", stats)

            # Ops-auth header should be attached when token is configured.
            ops_client = SteleClient(
                base_url=base_url,
                agent_id="agent-1",
                session_id="sess-1",
                ops_auth_token="ops-secret",
            )
            ops_client.bulk_forget({"ids": ["mem-1"]})
            self.assertEqual(RecordingHandler.last_bulk_forget_auth, "Bearer ops-secret")

            exported = ops_client.export_ndjson({})
            self.assertIn('"kind":"stele_export"', exported)
            self.assertEqual(RecordingHandler.last_export_auth, "Bearer ops-secret")

            self.assertIsNotNone(RecordingHandler.last_query)
            self.assertIsNone(RecordingHandler.last_health)
            self.assertEqual(RecordingHandler.last_query["query_text"], "hello")
            self.assertIsNotNone(RecordingHandler.last_protect)
            self.assertIsNotNone(RecordingHandler.last_supersede)
            self.assertIsNotNone(RecordingHandler.last_forget)
            self.assertIsNotNone(RecordingHandler.last_stats)
        finally:
            server.shutdown()
            server.server_close()
            thread.join(timeout=2)


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