"""Tests for the CONNECT tunnel (bvd/core/tunnel.py)."""
from __future__ import annotations

import asyncio
import ssl
from pathlib import Path
from unittest.mock import patch

import pytest

# ── Helpers ───────────────────────────────────────────────────────────────────

def _isolated_certs(tmp_path: Path):
    return patch("bvd.core.certs.settings.data_dir", tmp_path)


def _make_reader(data: bytes) -> asyncio.StreamReader:
    r = asyncio.StreamReader()
    r.feed_data(data)
    r.feed_eof()
    return r


class _FakeWriter:
    """Minimal StreamWriter stub that records what was written."""
    def __init__(self):
        self._buf = b""
        self.transport = _FakeTransport()
        self._closed = False

    def write(self, data: bytes) -> None:
        self._buf += data

    async def drain(self) -> None:
        pass

    def close(self) -> None:
        self._closed = True

    async def wait_closed(self) -> None:
        pass

    async def start_tls(self, ssl_ctx) -> None:
        pass  # no-op in tests


class _FakeTransport:
    def get_extra_info(self, key, default=None):
        return default


# ── _ssl_ctx_for_host ─────────────────────────────────────────────────────────

def test_ssl_ctx_for_host_cached(tmp_path):
    with _isolated_certs(tmp_path):
        from bvd.core.certs import generate_ca
        generate_ca()
        from bvd.core import tunnel
        tunnel._ssl_ctx_cache.clear()

        ctx1 = tunnel._ssl_ctx_for_host("api.anthropic.com")
        ctx2 = tunnel._ssl_ctx_for_host("api.anthropic.com")
        assert ctx1 is ctx2  # same object — cached

        ctx3 = tunnel._ssl_ctx_for_host("api.openai.com")
        assert ctx3 is not ctx1  # different domain


def test_ssl_ctx_for_host_is_server_context(tmp_path):
    with _isolated_certs(tmp_path):
        from bvd.core.certs import generate_ca
        generate_ca()
        from bvd.core import tunnel
        tunnel._ssl_ctx_cache.clear()

        ctx = tunnel._ssl_ctx_for_host("api.anthropic.com")
        assert isinstance(ctx, ssl.SSLContext)


# ── handle_client: non-CONNECT → forward to proxy ────────────────────────────

@pytest.mark.asyncio
async def test_handle_client_plain_http_forwards(tmp_path):
    """Plain HTTP on the tunnel port is forwarded to the HTTP proxy."""
    from bvd.core.tunnel import handle_client

    request = b"GET /v1/messages HTTP/1.1\r\nHost: api.anthropic.com\r\n\r\n"
    reader = _make_reader(request)
    writer = _FakeWriter()

    fake_resp = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"

    async def fake_open_conn(host, port):
        fake_reader = asyncio.StreamReader()
        fake_reader.feed_data(fake_resp)
        fake_reader.feed_eof()
        fake_writer = _FakeWriter()
        return fake_reader, fake_writer

    with patch("bvd.core.tunnel.asyncio.open_connection", side_effect=fake_open_conn):
        await handle_client(reader, writer)

    assert fake_resp in writer._buf


# ── handle_client: CONNECT to non-AI host → transparent pipe ─────────────────

@pytest.mark.asyncio
async def test_handle_client_connect_non_ai_transparent(tmp_path):
    """CONNECT to a non-AI host gets a 200 and is piped transparently."""
    from bvd.core.tunnel import handle_client

    request = b"CONNECT github.com:443 HTTP/1.1\r\nHost: github.com\r\n\r\n"
    reader = _make_reader(request)
    writer = _FakeWriter()

    async def fake_open_conn(host, port):
        fake_reader = asyncio.StreamReader()
        fake_reader.feed_eof()
        return fake_reader, _FakeWriter()

    with patch("bvd.core.tunnel.asyncio.open_connection", side_effect=fake_open_conn):
        with patch("bvd.core.tunnel.settings.target_apis", ["api.anthropic.com", "api.openai.com"]):
            await handle_client(reader, writer)

    assert b"200 Connection Established" in writer._buf


# ── handle_client: CONNECT to AI host → MITM path ───────────────────────────

@pytest.mark.asyncio
async def test_handle_client_connect_ai_host_mitm(tmp_path):
    """CONNECT to an AI API host sends 200 and attempts TLS MITM."""
    with _isolated_certs(tmp_path):
        from bvd.core.certs import generate_ca
        generate_ca()
        from bvd.core import tunnel
        tunnel._ssl_ctx_cache.clear()

        request = b"CONNECT api.anthropic.com:443 HTTP/1.1\r\nHost: api.anthropic.com\r\n\r\n"
        reader = _make_reader(request)
        writer = _FakeWriter()

        # start_tls is a no-op in _FakeWriter; _process_inner_http will see EOF immediately
        with patch("bvd.core.tunnel.settings.target_apis", ["api.anthropic.com"]):
            await tunnel.handle_client(reader, writer)

    assert b"200 Connection Established" in writer._buf
