import json
import threading
import uuid
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
import logging
import session_logger

class HookServerInstance(ThreadingHTTPServer):
    """Custom HTTPServer that carries a reference to the main App instance."""
    def __init__(self, server_address, RequestHandlerClass, app):
        super().__init__(server_address, RequestHandlerClass)
        self.app = app

class HookHandler(BaseHTTPRequestHandler):
    """Handles incoming HTTP requests for the API hooks."""
    def do_GET(self):
        app = self.server.app
        session_logger.log_api_hook("GET", self.path, "")
        if self.path == '/status':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps({'status': 'ok'}).encode('utf-8'))
        elif self.path == '/api/project':
            import project_manager
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            flat = project_manager.flat_config(app.project)
            self.wfile.write(json.dumps({'project': flat}).encode('utf-8'))
        elif self.path == '/api/session':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(
                json.dumps({'session': {'entries': app.disc_entries}}).
                encode('utf-8'))
        elif self.path == '/api/performance':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            metrics = {}
            if hasattr(app, 'perf_monitor'):
                metrics = app.perf_monitor.get_metrics()
            self.wfile.write(json.dumps({'performance': metrics}).encode('utf-8'))
        elif self.path == '/api/events':
            # Long-poll or return current event queue
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            events = []
            if hasattr(app, '_api_event_queue'):
                with app._api_event_queue_lock:
                    events = list(app._api_event_queue)
                    app._api_event_queue.clear()
            self.wfile.write(json.dumps({'events': events}).encode('utf-8'))
        elif self.path == '/api/gui/value':
            # POST with {"field": "field_tag"} to get value
            content_length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(content_length)
            data = json.loads(body.decode('utf-8'))
            field_tag = data.get("field")
            print(f"[DEBUG] Hook Server: get_value for {field_tag}")

            event = threading.Event()
            result = {"value": None}

            def get_val():
                try:
                    if field_tag in app._settable_fields:
                        attr = app._settable_fields[field_tag]
                        val = getattr(app, attr, None)
                        print(f"[DEBUG] Hook Server: attr={attr}, val={val}")
                        result["value"] = val
                    else:
                        print(f"[DEBUG] Hook Server: {field_tag} NOT in settable_fields")
                finally:
                    event.set()

            with app._pending_gui_tasks_lock:
                app._pending_gui_tasks.append({
                    "action": "custom_callback",
                    "callback": get_val
                })

            if event.wait(timeout=2):
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps(result).encode('utf-8'))
            else:
                self.send_response(504)
                self.end_headers()
        elif self.path.startswith('/api/gui/value/'):
            # Generic endpoint to get the value of any settable field
            field_tag = self.path.split('/')[-1]
            event = threading.Event()
            result = {"value": None}

            def get_val():
                try:
                    if field_tag in app._settable_fields:
                        attr = app._settable_fields[field_tag]
                        result["value"] = getattr(app, attr, None)
                finally:
                    event.set()

            with app._pending_gui_tasks_lock:
                app._pending_gui_tasks.append({
                    "action": "custom_callback",
                    "callback": get_val
                })

            if event.wait(timeout=2):
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps(result).encode('utf-8'))
            else:
                self.send_response(504)
                self.end_headers()
        elif self.path == '/api/gui/diagnostics':
            # Safe way to query multiple states at once via the main thread queue
            event = threading.Event()
            result = {}

            def check_all():
                try:
                    # Generic state check based on App attributes (works for both DPG and ImGui versions)
                    status = getattr(app, "ai_status", "idle")
                    result["thinking"] = status in ["sending...", "running powershell..."]
                    result["live"] = status in ["running powershell...", "fetching url...", "searching web...", "powershell done, awaiting AI..."]
                    result["prior"] = getattr(app, "is_viewing_prior_session", False)
                finally:
                    event.set()

            with app._pending_gui_tasks_lock:
                app._pending_gui_tasks.append({
                    "action": "custom_callback",
                    "callback": check_all
                })

            if event.wait(timeout=2):
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps(result).encode('utf-8'))
            else:
                self.send_response(504)
                self.end_headers()
                self.wfile.write(json.dumps({'error': 'timeout'}).encode('utf-8'))
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        app = self.server.app
        content_length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(content_length)
        body_str = body.decode('utf-8') if body else ""
        session_logger.log_api_hook("POST", self.path, body_str)

        try:
            data = json.loads(body_str) if body_str else {}
            if self.path == '/api/project':
                app.project = data.get('project', app.project)
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(
                    json.dumps({'status': 'updated'}).encode('utf-8'))
            elif self.path == '/api/session':
                app.disc_entries = data.get('session', {}).get(
                    'entries', app.disc_entries)
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(
                    json.dumps({'status': 'updated'}).encode('utf-8'))
            elif self.path == '/api/gui':
                with app._pending_gui_tasks_lock:
                    app._pending_gui_tasks.append(data)

                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(
                    json.dumps({'status': 'queued'}).encode('utf-8'))
            elif self.path == '/api/ask':
                request_id = str(uuid.uuid4())
                event = threading.Event()

                if not hasattr(app, '_pending_asks'):
                    app._pending_asks = {}
                if not hasattr(app, '_ask_responses'):
                    app._ask_responses = {}

                app._pending_asks[request_id] = event

                # Emit event for test/client discovery
                with app._api_event_queue_lock:
                    app._api_event_queue.append({
                        "type": "ask_received",
                        "request_id": request_id,
                        "data": data
                    })

                with app._pending_gui_tasks_lock:
                    app._pending_gui_tasks.append({
                        "type": "ask",
                        "request_id": request_id,
                        "data": data
                    })

                if event.wait(timeout=60.0):
                    response_data = app._ask_responses.get(request_id)
                    # Clean up response after reading
                    if request_id in app._ask_responses:
                        del app._ask_responses[request_id]
                    
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(json.dumps({'status': 'ok', 'response': response_data}).encode('utf-8'))
                else:
                    if request_id in app._pending_asks:
                        del app._pending_asks[request_id]
                    self.send_response(504)
                    self.end_headers()
                    self.wfile.write(json.dumps({'error': 'timeout'}).encode('utf-8'))

            elif self.path == '/api/ask/respond':
                request_id = data.get('request_id')
                response_data = data.get('response')

                if request_id and hasattr(app, '_pending_asks') and request_id in app._pending_asks:
                    app._ask_responses[request_id] = response_data
                    event = app._pending_asks[request_id]
                    event.set()

                    # Clean up pending ask entry
                    del app._pending_asks[request_id]

                    # Queue GUI task to clear the dialog
                    with app._pending_gui_tasks_lock:
                        app._pending_gui_tasks.append({
                            "action": "clear_ask",
                            "request_id": request_id
                        })

                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(json.dumps({'status': 'ok'}).encode('utf-8'))
                else:
                    self.send_response(404)
                    self.end_headers()
            else:
                self.send_response(404)
                self.end_headers()
        except Exception as e:
            self.send_response(500)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))

    def log_message(self, format, *args):
        logging.info("Hook API: " + format % args)

class HookServer:
    def __init__(self, app, port=8999):
        self.app = app
        self.port = port
        self.server = None
        self.thread = None

    def start(self):
        if self.thread and self.thread.is_alive():
            return

        is_gemini_cli = getattr(self.app, 'current_provider', '') == 'gemini_cli'
        if not getattr(self.app, 'test_hooks_enabled', False) and not is_gemini_cli:
            return

        # Ensure the app has the task queue and lock initialized
        if not hasattr(self.app, '_pending_gui_tasks'):
            self.app._pending_gui_tasks = []
        if not hasattr(self.app, '_pending_gui_tasks_lock'):
            self.app._pending_gui_tasks_lock = threading.Lock()

        # Initialize ask-related dictionaries
        if not hasattr(self.app, '_pending_asks'):
            self.app._pending_asks = {}
        if not hasattr(self.app, '_ask_responses'):
            self.app._ask_responses = {}

        # Event queue for test script subscriptions
        if not hasattr(self.app, '_api_event_queue'):
            self.app._api_event_queue = []
        if not hasattr(self.app, '_api_event_queue_lock'):
            self.app._api_event_queue_lock = threading.Lock()

        self.server = HookServerInstance(('127.0.0.1', self.port), HookHandler, self.app)
        self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
        self.thread.start()
        logging.info(f"Hook server started on port {self.port}")

    def stop(self):
        if self.server:
            self.server.shutdown()
            self.server.server_close()
            if self.thread:
                self.thread.join()
            logging.info("Hook server stopped")
