"""Unit tests for the mycli.config module."""

from io import BytesIO, StringIO, TextIOWrapper
import os
import struct
import sys
import tempfile
import pytest

from mycli.config import (
    get_mylogin_cnf_path,
    open_mylogin_cnf,
    read_and_decrypt_mylogin_cnf,
    read_config_file,
    str_to_bool,
    strip_matching_quotes,
)

LOGIN_PATH_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "mylogin.cnf"))


def open_bmylogin_cnf(name):
    """Open contents of *name* in a BytesIO buffer."""
    with open(name, "rb") as f:
        buf = BytesIO()
        buf.write(f.read())
    return buf


def test_read_mylogin_cnf():
    """Tests that a login path file can be read and decrypted."""
    mylogin_cnf = open_mylogin_cnf(LOGIN_PATH_FILE)

    assert isinstance(mylogin_cnf, TextIOWrapper)

    contents = mylogin_cnf.read()
    for word in ("[test]", "user", "password", "host", "port"):
        assert word in contents


def test_decrypt_blank_mylogin_cnf():
    """Test that a blank login path file is handled correctly."""
    mylogin_cnf = read_and_decrypt_mylogin_cnf(BytesIO())
    assert mylogin_cnf is None


def test_corrupted_login_key():
    """Test that a corrupted login path key is handled correctly."""
    buf = open_bmylogin_cnf(LOGIN_PATH_FILE)

    # Skip past the unused bytes
    buf.seek(4)

    # Write null bytes over half the login key
    buf.write(b"\0\0\0\0\0\0\0\0\0\0")

    buf.seek(0)
    mylogin_cnf = read_and_decrypt_mylogin_cnf(buf)

    assert mylogin_cnf is None


def test_corrupted_pad():
    """Tests that a login path file with a corrupted pad is partially read."""
    buf = open_bmylogin_cnf(LOGIN_PATH_FILE)

    # Skip past the login key
    buf.seek(24)

    # Skip option group
    len_buf = buf.read(4)
    (cipher_len,) = struct.unpack("<i", len_buf)
    buf.read(cipher_len)

    # Corrupt the pad for the user line
    len_buf = buf.read(4)
    (cipher_len,) = struct.unpack("<i", len_buf)
    buf.read(cipher_len - 1)
    buf.write(b"\0")

    buf.seek(0)
    mylogin_cnf = TextIOWrapper(read_and_decrypt_mylogin_cnf(buf))
    contents = mylogin_cnf.read()
    for word in ("[test]", "password", "host", "port"):
        assert word in contents
    assert "user" not in contents


def test_get_mylogin_cnf_path():
    """Tests that the path for .mylogin.cnf is detected."""
    original_env = None
    if "MYSQL_TEST_LOGIN_FILE" in os.environ:
        original_env = os.environ.pop("MYSQL_TEST_LOGIN_FILE")
    is_windows = sys.platform == "win32"

    login_cnf_path = get_mylogin_cnf_path()

    if original_env is not None:
        os.environ["MYSQL_TEST_LOGIN_FILE"] = original_env

    if login_cnf_path is not None:
        assert login_cnf_path.endswith(".mylogin.cnf")

        if is_windows is True:
            assert "MySQL" in login_cnf_path
        else:
            home_dir = os.path.expanduser("~")
            assert login_cnf_path.startswith(home_dir)


def test_alternate_get_mylogin_cnf_path():
    """Tests that the alternate path for .mylogin.cnf is detected."""
    original_env = None
    if "MYSQL_TEST_LOGIN_FILE" in os.environ:
        original_env = os.environ.pop("MYSQL_TEST_LOGIN_FILE")

    _, temp_path = tempfile.mkstemp()
    os.environ["MYSQL_TEST_LOGIN_FILE"] = temp_path

    login_cnf_path = get_mylogin_cnf_path()

    if original_env is not None:
        os.environ["MYSQL_TEST_LOGIN_FILE"] = original_env

    assert temp_path == login_cnf_path


def test_str_to_bool():
    """Tests that str_to_bool function converts values correctly."""

    assert str_to_bool(False) is False
    assert str_to_bool(True) is True
    assert str_to_bool("False") is False
    assert str_to_bool("True") is True
    assert str_to_bool("TRUE") is True
    assert str_to_bool("1") is True
    assert str_to_bool("0") is False
    assert str_to_bool("on") is True
    assert str_to_bool("off") is False
    assert str_to_bool("off") is False

    with pytest.raises(ValueError):
        str_to_bool("foo")

    with pytest.raises(TypeError):
        str_to_bool(None)


def test_read_config_file_list_values_default():
    """Test that reading a config file uses list_values by default."""

    f = StringIO("[main]\nweather='cloudy with a chance of meatballs'\n")
    config = read_config_file(f)

    assert config["main"]["weather"] == "cloudy with a chance of meatballs"


def test_read_config_file_list_values_off():
    """Test that you can disable list_values when reading a config file."""

    f = StringIO("[main]\nweather='cloudy with a chance of meatballs'\n")
    config = read_config_file(f, list_values=False)

    assert config["main"]["weather"] == "'cloudy with a chance of meatballs'"


def test_strip_quotes_with_matching_quotes():
    """Test that a string with matching quotes is unquoted."""

    s = "May the force be with you."
    assert s == strip_matching_quotes('"{}"'.format(s))
    assert s == strip_matching_quotes("'{}'".format(s))


def test_strip_quotes_with_unmatching_quotes():
    """Test that a string with unmatching quotes is not unquoted."""

    s = "May the force be with you."
    assert '"' + s == strip_matching_quotes('"{}'.format(s))
    assert s + "'" == strip_matching_quotes("{}'".format(s))


def test_strip_quotes_with_empty_string():
    """Test that an empty string is handled during unquoting."""

    assert "" == strip_matching_quotes("")


def test_strip_quotes_with_none():
    """Test that None is handled during unquoting."""

    assert None is strip_matching_quotes(None)


def test_strip_quotes_with_quotes():
    """Test that strings with quotes in them are handled during unquoting."""

    s1 = 'Darth Vader said, "Luke, I am your father."'
    assert s1 == strip_matching_quotes(s1)

    s2 = '"Darth Vader said, "Luke, I am your father.""'
    assert s2[1:-1] == strip_matching_quotes(s2)