# -*- coding: utf-8 -*-
"""Test that MRFHandler intercepts DNS (port 53) via DoH while
forwarding other UDP traffic normally."""
import socket
import struct
import threading
from unittest.mock import patch

import pytest

from pymrf4.mrf_proxy import MRFHandler, MRFServer
from tests.conftest import (
    _free_port,
    socks5_handshake,
    socks5_udp_associate,
    build_socks5_udp_packet,
    parse_socks5_udp_packet,
    build_dns_query,
)


@pytest.fixture()
def mrf_server():
    """Start an MRFServer with MRFHandler on a random port."""
    port = _free_port()
    server = MRFServer(("127.0.0.1", port), MRFHandler)
    server.set_config({})  # no tracker configs needed for DNS test
    t = threading.Thread(target=server.serve_forever, daemon=True)
    t.start()
    yield "127.0.0.1", port
    server.shutdown()


@pytest.fixture()
def udp_echo_server():
    """Simple UDP echo server."""
    port = _free_port()
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("127.0.0.1", port))

    def _serve():
        while True:
            try:
                data, addr = sock.recvfrom(4096)
                if data:
                    sock.sendto(data, addr)
            except OSError:
                break

    t = threading.Thread(target=_serve, daemon=True)
    t.start()
    yield "127.0.0.1", port
    sock.close()


def test_mrf_dns_via_doh(mrf_server):
    """DNS queries on port 53 should be resolved via DoH, not forwarded."""
    proxy_host, proxy_port = mrf_server

    with socket.create_connection((proxy_host, proxy_port), timeout=5) as tcp:
        socks5_handshake(tcp)
        _, relay_addr, relay_port = socks5_udp_associate(tcp)

        udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_client.settimeout(5)
        try:
            dns_query = build_dns_query("example.com", txn_id=0xABCD)
            # Send DNS to 8.8.8.8:53 — MRFHandler should intercept and use DoH
            pkt = build_socks5_udp_packet("8.8.8.8", 53, dns_query)

            with patch.object(MRFHandler, "resolve_domain_doh", return_value="93.184.216.34"):
                udp_client.sendto(pkt, ("127.0.0.1", relay_port))
                reply_data, _ = udp_client.recvfrom(4096)

            addr, port, dns_reply = parse_socks5_udp_packet(reply_data)

            # Verify DNS response structure
            assert len(dns_reply) >= 12
            # Transaction ID should match
            assert struct.unpack("!H", dns_reply[:2])[0] == 0xABCD
            # QR bit should be set (response)
            flags = struct.unpack("!H", dns_reply[2:4])[0]
            assert flags & 0x8000
            # Answer should contain 93.184.216.34
            assert socket.inet_aton("93.184.216.34") in dns_reply
        finally:
            udp_client.close()


def test_mrf_non_dns_udp_forward(mrf_server, udp_echo_server):
    """Non-DNS UDP traffic should be forwarded normally (not intercepted)."""
    proxy_host, proxy_port = mrf_server
    echo_host, echo_port = udp_echo_server

    with socket.create_connection((proxy_host, proxy_port), timeout=5) as tcp:
        socks5_handshake(tcp)
        _, relay_addr, relay_port = socks5_udp_associate(tcp)

        udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_client.settimeout(5)
        try:
            payload = b"not a dns packet"
            pkt = build_socks5_udp_packet(echo_host, echo_port, payload)
            udp_client.sendto(pkt, ("127.0.0.1", relay_port))

            reply_data, _ = udp_client.recvfrom(4096)
            addr, port, echoed = parse_socks5_udp_packet(reply_data)
            assert echoed == payload
        finally:
            udp_client.close()
