from __future__ import annotations
import asyncio

from ..protocol import constants as C
from ..protocol import wire


async def probe_secure_renegotiation(host: str, port: int, sni: str | None, timeout: float = 6.0) -> tuple[bool, str | None]:
    """Check whether the server returns the renegotiation_info extension (RFC 5746).

    Returns (secure_renego_supported, error).
    """
    try:
        reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=timeout)
    except Exception as e:
        return False, f"connect: {e}"
    try:
        exts = (
            (wire.ext_server_name(sni) if sni and not wire._is_ip_literal(sni) else b"")
            + wire.ext_ec_point_formats()
            + wire.ext_supported_groups([0x001d, 0x0017, 0x0018])
            + wire.ext_signature_algorithms([0x0403, 0x0401])
            + wire.ext_renegotiation_info_empty()
        )
        ch = wire.build_client_hello(
            record_version=C.TLS_1_0,
            client_hello_version=C.TLS_1_2,
            hostname=sni,
            cipher_suites=[0xc02f, 0xc030, 0xc013, 0xc014, 0x002f, 0x0035, 0x000a],
            extensions=exts,
        )
        writer.write(ch)
        try:
            await asyncio.wait_for(writer.drain(), timeout=timeout)
        except Exception:
            pass
        try:
            data = await asyncio.wait_for(reader.read(8192), timeout=timeout)
        except Exception:
            data = b""
        if not data:
            return False, "no response"
        parsed = wire.parse_server_response(data)
        if parsed is None or parsed.extensions is None:
            return False, "no server hello"
        # Either renegotiation_info extension or SCSV cipher (0x00ff) acknowledgment.
        if C.EXT_RENEGOTIATION_INFO in parsed.extensions:
            return True, None
        return False, "no renegotiation_info extension"
    finally:
        try:
            writer.close()
            await asyncio.wait_for(writer.wait_closed(), timeout=1.0)
        except Exception:
            pass


async def probe_fallback_scsv(host: str, port: int, sni: str | None, timeout: float = 6.0) -> tuple[bool, str | None]:
    """Check whether the server rejects TLS_FALLBACK_SCSV.

    We offer TLS 1.1 + SCSV (0x5600); if the server supports a higher version,
    it MUST respond with inappropriate_fallback(86) alert.
    """
    try:
        reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=timeout)
    except Exception as e:
        return False, f"connect: {e}"
    try:
        exts = (
            (wire.ext_server_name(sni) if sni and not wire._is_ip_literal(sni) else b"")
            + wire.ext_ec_point_formats()
            + wire.ext_supported_groups([0x001d, 0x0017, 0x0018])
            + wire.ext_signature_algorithms([0x0403, 0x0401])
            + wire.ext_renegotiation_info_empty()
        )
        ch = wire.build_client_hello(
            record_version=C.TLS_1_0,
            client_hello_version=C.TLS_1_1,
            hostname=sni,
            cipher_suites=[0x5600, 0xc013, 0xc014, 0x002f, 0x0035],
            extensions=exts,
        )
        writer.write(ch)
        try:
            await asyncio.wait_for(writer.drain(), timeout=timeout)
        except Exception:
            pass
        try:
            data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
        except Exception:
            data = b""
        if not data:
            return False, "no response"
        # Look for alert 86 (inappropriate_fallback) = supported (good).
        i = 0
        while i + 5 <= len(data):
            ct = data[i]
            rec_len = (data[i + 3] << 8) | data[i + 4]
            body = data[i + 5:i + 5 + rec_len]
            if ct == C.CT_ALERT and len(body) >= 2 and body[1] == 86:
                return True, None
            i += 5 + rec_len
            if rec_len == 0:
                break
        return False, "no inappropriate_fallback alert"
    finally:
        try:
            writer.close()
            await asyncio.wait_for(writer.wait_closed(), timeout=1.0)
        except Exception:
            pass
