"""Test NEW_ENVIRON, rfc-1572_."""

# std imports
import asyncio

# 3rd party
import pytest

# local
import telnetlib3
import telnetlib3.stream_writer
from telnetlib3.telopt import DO, IS, SB, SE, IAC, VAR, WILL, TTYPE, USERVAR, NEW_ENVIRON
from telnetlib3.tests.accessories import create_server, open_connection, asyncio_connection


async def test_telnet_server_on_environ(bind_host, unused_tcp_port):
    """Test Server's callback method on_environ()."""
    _waiter = asyncio.Future()

    class ServerTestEnviron(telnetlib3.TelnetServer):
        def on_environ(self, mapping):
            super().on_environ(mapping)
            _waiter.set_result(self)

    async with create_server(
        protocol_factory=ServerTestEnviron, host=bind_host, port=unused_tcp_port
    ):
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WILL + NEW_ENVIRON)
            writer.write(
                IAC
                + SB
                + NEW_ENVIRON
                + IS
                + telnetlib3.stream_writer._encode_env_buf(
                    {"aLpHa": "oMeGa", "beta": "b", "gamma": "".join(chr(n) for n in range(0, 128))}
                )
                + IAC
                + SE
            )

            srv_instance = await asyncio.wait_for(_waiter, 0.5)
            assert srv_instance.get_extra_info("ALPHA") == "oMeGa"
            assert srv_instance.get_extra_info("BETA") == "b"
            assert srv_instance.get_extra_info("GAMMA") == ("".join(chr(n) for n in range(0, 128)))


async def test_telnet_client_send_environ(bind_host, unused_tcp_port):
    """Test Client's callback method send_environ() for specific requests."""
    _waiter = asyncio.Future()
    given_cols = 19
    given_rows = 84
    given_encoding = "cp437"
    given_term = "vt220"

    class ServerTestEnviron(telnetlib3.TelnetServer):
        def on_environ(self, mapping):
            super().on_environ(mapping)
            _waiter.set_result(mapping)

    async with create_server(
        protocol_factory=ServerTestEnviron, host=bind_host, port=unused_tcp_port
    ):
        async with open_connection(
            host=bind_host,
            port=unused_tcp_port,
            cols=given_cols,
            rows=given_rows,
            encoding=given_encoding,
            term=given_term,
        ) as (reader, writer):
            mapping = await asyncio.wait_for(_waiter, 0.5)
            assert mapping["COLUMNS"] == str(given_cols)
            assert mapping["LANG"] == "en_US." + given_encoding
            assert mapping["LINES"] == str(given_rows)
            assert mapping["TERM"] == "vt220"


async def test_telnet_client_send_var_uservar_environ(bind_host, unused_tcp_port):
    """Test Client's callback method send_environ() for VAR/USERVAR request."""
    _waiter = asyncio.Future()
    given_cols = 19
    given_rows = 84
    given_encoding = "cp437"
    given_term = "vt220"

    class ServerTestEnviron(telnetlib3.TelnetServer):
        def on_environ(self, mapping):
            super().on_environ(mapping)
            _waiter.set_result(mapping)

        def on_request_environ(self):
            return [VAR, USERVAR]

    async with create_server(
        protocol_factory=ServerTestEnviron, host=bind_host, port=unused_tcp_port
    ):
        async with open_connection(
            host=bind_host,
            port=unused_tcp_port,
            cols=given_cols,
            rows=given_rows,
            encoding=given_encoding,
            term=given_term,
            connect_maxwait=0.5,
        ) as (reader, writer):
            mapping = await asyncio.wait_for(_waiter, 0.5)
            assert mapping == {}


async def test_telnet_server_reject_environ(bind_host, unused_tcp_port):
    """Test Client's callback method send_environ() for specific requests."""
    given_cols = 19
    given_rows = 84
    given_term = "vt220"

    class ServerTestEnviron(telnetlib3.TelnetServer):
        def on_request_environ(self):
            return None

    async with create_server(
        protocol_factory=ServerTestEnviron,
        host=bind_host,
        port=unused_tcp_port,
        encoding=False,
        connect_maxwait=0.15,
    ):
        async with open_connection(
            host=bind_host,
            port=unused_tcp_port,
            cols=given_cols,
            rows=given_rows,
            encoding=False,
            term=given_term,
            connect_minwait=0.1,
            connect_maxwait=0.15,
        ) as (reader, writer):
            _failed = {key: val for key, val in writer.pending_option.items() if val}
            assert _failed == {SB + NEW_ENVIRON: True}


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

    def write(self, data):
        pass

    def is_closing(self):
        return False


def _make_server():
    server = telnetlib3.TelnetServer()
    server.connection_made(_MockTransport())
    return server


@pytest.mark.parametrize(
    "ttype1,ttype2",
    [
        ("ANSI", "VT100"),
        ("ANSI", "ANSI"),
        ("ansi", "vt100"),
        ("xterm", "xterm"),
        ("xterm", "xterm-256color"),
    ],
)
async def test_negotiate_environ_always_sent(ttype1, ttype2):
    """DO NEW_ENVIRON is always sent regardless of client identity."""
    server = _make_server()
    server._extra["ttype1"] = ttype1
    server._extra["ttype2"] = ttype2
    server._negotiate_environ()
    assert server.writer.pending_option.get(DO + NEW_ENVIRON)


@pytest.mark.parametrize(
    "ttype1,ttype2,expect_user",
    [
        ("ANSI", "VT100", False),
        ("ANSI", "ANSI", True),
        ("ansi", "vt100", True),
        ("xterm", "xterm", True),
        ("xterm", "xterm-256color", True),
    ],
)
async def test_on_request_environ_user_excluded_for_ms_telnet(ttype1, ttype2, expect_user):
    """USER is excluded from NEW_ENVIRON request for Microsoft telnet."""
    server = _make_server()
    server._extra["ttype1"] = ttype1
    server._extra["ttype2"] = ttype2
    result = server.on_request_environ()
    assert ("USER" in result) is expect_user
    assert "LOGNAME" in result


@pytest.mark.parametrize("ttype_refused,final", [(True, False), (False, True)])
async def test_check_negotiation_triggers_environ(ttype_refused, final):
    """check_negotiation sends DO NEW_ENVIRON on TTYPE refusal or final."""
    server = _make_server()
    server._advanced = True
    if ttype_refused:
        server.writer.remote_option[TTYPE] = False
    server.check_negotiation(final=final)
    assert server._environ_requested
    assert server.writer.pending_option.get(DO + NEW_ENVIRON)


async def test_check_negotiation_no_advanced_skips_environ():
    """check_negotiation does not send DO NEW_ENVIRON without advanced."""
    server = _make_server()
    server.writer.remote_option[TTYPE] = False
    server.check_negotiation(final=True)
    assert not server._environ_requested
    assert not server.writer.pending_option.get(DO + NEW_ENVIRON)


@pytest.mark.parametrize("ttype,expect_requested", [("xterm", True), ("ANSI", False)])
async def test_on_ttype_environ_behavior(ttype, expect_requested):
    """on_ttype sends DO NEW_ENVIRON for non-ANSI, defers for ANSI."""
    server = _make_server()
    server.on_ttype(ttype)
    assert server._environ_requested is expect_requested
    assert bool(server.writer.pending_option.get(DO + NEW_ENVIRON)) is expect_requested


@pytest.mark.parametrize(
    "env,expect_force",
    [
        ({"LANG": "en_US.UTF-8"}, True),
        ({"LANG": "ja_JP.EUC-JP"}, True),
        ({"CHARSET": "UTF-8"}, True),
        ({"CHARSET": "ISO-8859-1", "USER": "test"}, True),
        ({"LANG": "en_US"}, False),
        ({"LANG": "C"}, False),
        ({"USER": "test", "TERM": "xterm"}, False),
        ({}, False),
    ],
)
async def test_on_environ_force_binary(env, expect_force):
    """on_environ sets force_binary when LANG has encoding or CHARSET is present."""
    server = _make_server()
    assert server.force_binary is False
    server.on_environ(dict(env))
    assert server.force_binary is expect_force
