"""Synthetic loopback MCP transport fixture, not a production integration.

Set DOCS_MCP_FIXTURE_TOKEN to a synthetic test value, then run this script.
Prints its loopback port; supports /sse and /mcp using the same read-only tool.
No external files, accounts or services are accessed. Stop with Ctrl+C.
"""
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import argparse
import os
import queue
import threading
from urllib.parse import urlsplit, parse_qs
import uuid


class Fixture:
    def __init__(self, token, port=0):
        self.token = token
        self.mode = 'normal'
        self.requests = []
        self.streams = {}
        fixture = self

        class Handler(BaseHTTPRequestHandler):
            def log_message(self, *args):
                pass

            def admit(self):
                matched = self.headers.get('Authorization', '') == 'Bearer ' + fixture.token
                self.record = {'method': self.command, 'path': urlsplit(self.path).path,
                               'auth_matches': matched, 'mode': fixture.mode}
                fixture.requests.append(self.record)
                if fixture.mode == 'expired' or not matched:
                    self.send_response(401)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(b'{"error":"fixture credential expired or absent"}')
                    return False
                return True

            def do_GET(self):
                if not self.admit():
                    return
                if urlsplit(self.path).path != '/sse':
                    self.send_response(405)
                    self.end_headers()
                    return
                session = str(uuid.uuid4())
                messages = queue.Queue()
                fixture.streams[session] = messages
                self.send_response(200)
                self.send_header('Content-Type', 'text/event-stream')
                self.send_header('Cache-Control', 'no-cache')
                self.end_headers()
                try:
                    self.wfile.write(('event: endpoint\ndata: /messages?session=' + session + '\n\n').encode())
                    self.wfile.flush()
                    while True:
                        try:
                            message = messages.get(timeout=1)
                        except queue.Empty:
                            self.wfile.write(b': heartbeat\n\n')
                        else:
                            self.wfile.write(('event: message\ndata: ' + json.dumps(message) + '\n\n').encode())
                        self.wfile.flush()
                except (BrokenPipeError, ConnectionResetError):
                    pass
                finally:
                    fixture.streams.pop(session, None)

            def do_DELETE(self):
                if self.admit():
                    self.send_response(200)
                    self.end_headers()

            def do_POST(self):
                if not self.admit():
                    return
                request = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
                self.record['rpc_method'] = request.get('method')
                if 'id' not in request:
                    self.send_response(202)
                    self.end_headers()
                    return
                method = request.get('method')
                if method == 'initialize':
                    result = {'protocolVersion': '2024-11-05',
                              'capabilities': {'tools': {}}, 'serverInfo': {'name': 'docs-transport-fixture', 'version': '1'}}
                elif method == 'tools/list':
                    result = {'tools': [{'name': 'note_lookup', 'description': 'Read a synthetic approved note.',
                                         'inputSchema': {'type': 'object', 'properties': {'collection': {'type': 'string'}, 'id': {'type': 'string'}},
                                                         'required': ['collection', 'id'], 'additionalProperties': False}}]}
                elif method == 'tools/call':
                    args = request.get('params', {}).get('arguments', {})
                    self.record['arguments'] = args
                    denied = args.get('collection') != 'approved'
                    missing = args.get('id') != 'release-checklist'
                    text = 'out_of_scope' if denied else 'not_found' if missing else 'Review the change, run the checks, and record the outcome before publishing.'
                    result = {'content': [{'type': 'text', 'text': text}], 'isError': denied or missing}
                elif method == 'ping':
                    result = {}
                else:
                    result = None
                response = {'jsonrpc': '2.0', 'id': request['id'], 'result': result} if result is not None else {
                    'jsonrpc': '2.0', 'id': request['id'], 'error': {'code': -32601, 'message': 'Method not found'}}
                if fixture.mode == 'malformed':
                    response = {'jsonrpc': '2.0', 'id': request['id'], 'result': {'tools': 'invalid-shape'}}
                session = parse_qs(urlsplit(self.path).query).get('session', [''])[0]
                if urlsplit(self.path).path == '/messages':
                    self.send_response(202)
                    self.end_headers()
                    if session in fixture.streams:
                        fixture.streams[session].put(response)
                else:
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(json.dumps(response).encode())

        self.server = ThreadingHTTPServer(('127.0.0.1', port), Handler)
        self.server.daemon_threads = True

    def start(self):
        threading.Thread(target=self.server.serve_forever, daemon=True).start()
        return 'http://127.0.0.1:' + str(self.server.server_port)

    def stop(self):
        self.server.shutdown()
        self.server.server_close()


if __name__ == '__main__':
    token = os.environ.get('DOCS_MCP_FIXTURE_TOKEN')
    if not token:
        raise SystemExit('Set DOCS_MCP_FIXTURE_TOKEN to a synthetic fixture-only value')
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--port', type=int, default=8766)
    args = parser.parse_args()
    fixture = Fixture(token, args.port)
    print(fixture.start(), flush=True)
    try:
        threading.Event().wait()
    except KeyboardInterrupt:
        fixture.stop()
