import pytest
from textwrap import dedent
import re
from .conftest import (
    tick_box,
    info_box,
    cross_box,
    mock_command,
    mock_command_run,
    mock_command_2,
    mock_command_passthrough,
    run_script,
)

FTL_BRANCH = "development-v6"


def test_supported_package_manager(host):
    """
    confirm installer exits when no supported package manager found
    """
    # break supported package managers
    host.run("rm -rf /usr/bin/apt-get")
    host.run("rm -rf /usr/bin/rpm")
    package_manager_detect = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    """
    )
    expected_stdout = cross_box + " No supported package manager found"
    assert expected_stdout in package_manager_detect.stdout
    # assert package_manager_detect.rc == 1


def test_selinux_not_detected(host):
    """
    confirms installer continues when SELinux configuration file does not exist
    """
    check_selinux = host.run(
        """
    rm -f /etc/selinux/config
    source /opt/pihole/basic-install.sh
    checkSelinux
    """
    )
    expected_stdout = info_box + " SELinux not detected"
    assert expected_stdout in check_selinux.stdout
    assert check_selinux.rc == 0


def get_directories_recursive(host, directory):
    if directory is None:
        return directory
    # returns all non-hidden subdirs of 'directory'
    dirs_raw = host.run("find {} -type d -not -path '*/.*'".format(directory))
    dirs = list(filter(bool, dirs_raw.stdout.splitlines()))
    return dirs


def test_installPihole_fresh_install_readableFiles(host):
    """
    confirms all necessary files are readable by pihole user
    """
    # dialog returns Cancel for user prompt
    mock_command("dialog", {"*": ("", "0")}, host)
    # mock git pull
    mock_command_passthrough("git", {"pull": ("", "0")}, host)
    # mock systemctl to not start FTL
    mock_command_2(
        "systemctl",
        {
            "enable pihole-FTL": ("", "0"),
            "restart pihole-FTL": ("", "0"),
            "start pihole-FTL": ("", "0"),
            "*": ('echo "systemctl call with $@"', "0"),
        },
        host,
    )
    # try to install man
    host.run("command -v apt-get > /dev/null && apt-get install -qq man")
    host.run("command -v dnf > /dev/null && dnf install -y man")
    host.run("command -v yum > /dev/null && yum install -y man")
    # Workaround to get FTLv6 installed until it reaches master branch
    host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
    install = host.run(
        """
    export TERM=xterm
    export DEBIAN_FRONTEND=noninteractive
    umask 0027
    runUnattended=true
    useUpdateVars=true
    source /opt/pihole/basic-install.sh > /dev/null
    runUnattended=true
    useUpdateVars=true
    main
    /opt/pihole/pihole-FTL-prestart.sh
    """
    )
    assert 0 == install.rc
    maninstalled = True
    if (info_box + " man not installed") in install.stdout:
        maninstalled = False
    if (info_box + " man pages not installed") in install.stdout:
        maninstalled = False
    piholeuser = "pihole"
    exit_status_success = 0
    test_cmd = 'su --shell /bin/bash --command "test -{0} {1}" -p {2}'
    # check files in /etc/pihole for read, write and execute permission
    check_etc = test_cmd.format("r", "/etc/pihole", piholeuser)
    actual_rc = host.run(check_etc).rc
    assert exit_status_success == actual_rc
    check_etc = test_cmd.format("x", "/etc/pihole", piholeuser)
    actual_rc = host.run(check_etc).rc
    assert exit_status_success == actual_rc
    # readable and writable dhcp.leases
    check_leases = test_cmd.format("r", "/etc/pihole/dhcp.leases", piholeuser)
    actual_rc = host.run(check_leases).rc
    assert exit_status_success == actual_rc
    check_leases = test_cmd.format("w", "/etc/pihole/dhcp.leases", piholeuser)
    actual_rc = host.run(check_leases).rc
    # readable dns-servers.conf
    assert exit_status_success == actual_rc
    check_servers = test_cmd.format("r", "/etc/pihole/dns-servers.conf", piholeuser)
    actual_rc = host.run(check_servers).rc
    assert exit_status_success == actual_rc
    # readable install.log
    check_install = test_cmd.format("r", "/etc/pihole/install.log", piholeuser)
    actual_rc = host.run(check_install).rc
    assert exit_status_success == actual_rc
    # readable versions
    check_localversion = test_cmd.format("r", "/etc/pihole/versions", piholeuser)
    actual_rc = host.run(check_localversion).rc
    assert exit_status_success == actual_rc
    # readable logrotate
    check_logrotate = test_cmd.format("r", "/etc/pihole/logrotate", piholeuser)
    actual_rc = host.run(check_logrotate).rc
    assert exit_status_success == actual_rc
    # readable macvendor.db
    check_macvendor = test_cmd.format("r", "/etc/pihole/macvendor.db", piholeuser)
    actual_rc = host.run(check_macvendor).rc
    assert exit_status_success == actual_rc
    # check readable and executable /etc/init.d/pihole-FTL
    check_init = test_cmd.format("x", "/etc/init.d/pihole-FTL", piholeuser)
    actual_rc = host.run(check_init).rc
    assert exit_status_success == actual_rc
    check_init = test_cmd.format("r", "/etc/init.d/pihole-FTL", piholeuser)
    actual_rc = host.run(check_init).rc
    assert exit_status_success == actual_rc
    # check readable and executable manpages
    if maninstalled is True:
        check_man = test_cmd.format("x", "/usr/local/share/man", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format("r", "/usr/local/share/man", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format("x", "/usr/local/share/man/man8", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format("r", "/usr/local/share/man/man8", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format("x", "/usr/local/share/man/man5", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format("r", "/usr/local/share/man/man5", piholeuser)
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
        check_man = test_cmd.format(
            "r", "/usr/local/share/man/man8/pihole.8", piholeuser
        )
        actual_rc = host.run(check_man).rc
        assert exit_status_success == actual_rc
    # check not readable cron file
    check_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser)
    actual_rc = host.run(check_sudo).rc
    assert exit_status_success == actual_rc
    check_sudo = test_cmd.format("r", "/etc/cron.d/", piholeuser)
    actual_rc = host.run(check_sudo).rc
    assert exit_status_success == actual_rc
    check_sudo = test_cmd.format("r", "/etc/cron.d/pihole", piholeuser)
    actual_rc = host.run(check_sudo).rc
    assert exit_status_success == actual_rc
    directories = get_directories_recursive(host, "/etc/.pihole/")
    for directory in directories:
        check_pihole = test_cmd.format("r", directory, piholeuser)
        actual_rc = host.run(check_pihole).rc
        check_pihole = test_cmd.format("x", directory, piholeuser)
        actual_rc = host.run(check_pihole).rc
        findfiles = 'find "{}" -maxdepth 1 -type f  -exec echo {{}} \\;;'
        filelist = host.run(findfiles.format(directory))
        files = list(filter(bool, filelist.stdout.splitlines()))
        for file in files:
            check_pihole = test_cmd.format("r", file, piholeuser)
            actual_rc = host.run(check_pihole).rc


def test_update_package_cache_success_no_errors(host):
    """
    confirms package cache was updated without any errors
    """
    updateCache = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    update_package_cache
    """
    )
    expected_stdout = tick_box + " Update local cache of available packages"
    assert expected_stdout in updateCache.stdout
    assert "error" not in updateCache.stdout.lower()


def test_update_package_cache_failure_no_errors(host):
    """
    confirms package cache was not updated
    """
    mock_command("apt-get", {"update": ("", "1")}, host)
    updateCache = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    update_package_cache
    """
    )
    expected_stdout = cross_box + " Update local cache of available packages"
    assert expected_stdout in updateCache.stdout
    assert "Error: Unable to update package cache." in updateCache.stdout


@pytest.mark.parametrize(
    "arch,detected_string,supported",
    [
        ("aarch64", "AArch64 (64 Bit ARM)", True),
        ("armv6", "ARMv6", True),
        ("armv7l", "ARMv7 (or newer)", True),
        ("armv7", "ARMv7 (or newer)", True),
        ("armv8a", "ARMv7 (or newer)", True),
        ("x86_64", "x86_64", True),
        ("riscv64", "riscv64", True),
        ("mips", "mips", False),
    ],
)
def test_FTL_detect_no_errors(host, arch, detected_string, supported):
    """
    confirms only correct package is downloaded for FTL engine
    """
    # mock uname to return passed platform
    mock_command("uname", {"-m": (arch, "0")}, host)
    # mock readelf to respond with passed CPU architecture
    mock_command_2(
        "readelf",
        {
            "-A /bin/sh": ("Tag_CPU_arch: " + arch, "0"),
            "-A /usr/bin/sh": ("Tag_CPU_arch: " + arch, "0"),
        },
        host,
    )
    host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    create_pihole_user
    funcOutput=$(get_binary_name)
    binary="pihole-FTL${funcOutput##*pihole-FTL}"
    theRest="${funcOutput%pihole-FTL*}"
    FTLdetect "${binary}" "${theRest}"
    """
    )
    if supported:
        expected_stdout = info_box + " FTL Checks..."
        assert expected_stdout in detectPlatform.stdout
        expected_stdout = tick_box + " Detected " + detected_string + " architecture"
        assert expected_stdout in detectPlatform.stdout
        expected_stdout = tick_box + " Downloading and Installing FTL"
        assert expected_stdout in detectPlatform.stdout
    else:
        expected_stdout = (
            "Not able to detect architecture (unknown: " + detected_string + ")"
        )
        assert expected_stdout in detectPlatform.stdout


def test_FTL_development_binary_installed_and_responsive_no_errors(host):
    """
    confirms FTL development binary is copied and functional in installed location
    """
    host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
    host.run(
        """
    source /opt/pihole/basic-install.sh
    create_pihole_user
    funcOutput=$(get_binary_name)
    binary="pihole-FTL${funcOutput##*pihole-FTL}"
    theRest="${funcOutput%pihole-FTL*}"
    FTLdetect "${binary}" "${theRest}"
    """
    )
    version_check = host.run(
        """
    VERSION=$(pihole-FTL version)
    echo ${VERSION:0:1}
    """
    )
    expected_stdout = "v"
    assert expected_stdout in version_check.stdout


def test_IPv6_only_link_local(host):
    """
    confirms IPv6 blocking is disabled for Link-local address
    """
    # mock ip -6 address to return Link-local address
    mock_command_2(
        "ip",
        {"-6 address": ("inet6 fe80::d210:52fa:fe00:7ad7/64 scope link", "0")},
        host,
    )
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    find_IPv6_information
    """
    )
    expected_stdout = "Unable to find IPv6 ULA/GUA address"
    assert expected_stdout in detectPlatform.stdout


def test_IPv6_only_ULA(host):
    """
    confirms IPv6 blocking is enabled for ULA addresses
    """
    # mock ip -6 address to return ULA address
    mock_command_2(
        "ip",
        {
            "-6 address": (
                "inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
                "0",
            )
        },
        host,
    )
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    find_IPv6_information
    """
    )
    expected_stdout = "Found IPv6 ULA address"
    assert expected_stdout in detectPlatform.stdout


def test_IPv6_only_GUA(host):
    """
    confirms IPv6 blocking is enabled for GUA addresses
    """
    # mock ip -6 address to return GUA address
    mock_command_2(
        "ip",
        {
            "-6 address": (
                "inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
                "0",
            )
        },
        host,
    )
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    find_IPv6_information
    """
    )
    expected_stdout = "Found IPv6 GUA address"
    assert expected_stdout in detectPlatform.stdout


def test_IPv6_GUA_ULA_test(host):
    """
    confirms IPv6 blocking is enabled for GUA and ULA addresses
    """
    # mock ip -6 address to return GUA and ULA addresses
    mock_command_2(
        "ip",
        {
            "-6 address": (
                "inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n"
                "inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
                "0",
            )
        },
        host,
    )
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    find_IPv6_information
    """
    )
    expected_stdout = "Found IPv6 ULA address"
    assert expected_stdout in detectPlatform.stdout


def test_IPv6_ULA_GUA_test(host):
    """
    confirms IPv6 blocking is enabled for GUA and ULA addresses
    """
    # mock ip -6 address to return ULA and GUA addresses
    mock_command_2(
        "ip",
        {
            "-6 address": (
                "inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n"
                "inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
                "0",
            )
        },
        host,
    )
    detectPlatform = host.run(
        """
    source /opt/pihole/basic-install.sh
    find_IPv6_information
    """
    )
    expected_stdout = "Found IPv6 ULA address"
    assert expected_stdout in detectPlatform.stdout


def test_validate_ip(host):
    """
    Tests valid_ip for various IP addresses
    """

    def test_address(addr, success=True):
        output = host.run(
            """
        source /opt/pihole/basic-install.sh
        valid_ip "{addr}"
        """.format(
                addr=addr
            )
        )

        assert output.rc == 0 if success else 1

    test_address("192.168.1.1")
    test_address("127.0.0.1")
    test_address("255.255.255.255")
    test_address("255.255.255.256", False)
    test_address("255.255.256.255", False)
    test_address("255.256.255.255", False)
    test_address("256.255.255.255", False)
    test_address("1092.168.1.1", False)
    test_address("not an IP", False)
    test_address("8.8.8.8#", False)
    test_address("8.8.8.8#0")
    test_address("8.8.8.8#1")
    test_address("8.8.8.8#42")
    test_address("8.8.8.8#888")
    test_address("8.8.8.8#1337")
    test_address("8.8.8.8#65535")
    test_address("8.8.8.8#65536", False)
    test_address("8.8.8.8#-1", False)
    test_address("00.0.0.0", False)
    test_address("010.0.0.0", False)
    test_address("001.0.0.0", False)
    test_address("0.0.0.0#00", False)
    test_address("0.0.0.0#01", False)
    test_address("0.0.0.0#001", False)
    test_address("0.0.0.0#0001", False)
    test_address("0.0.0.0#00001", False)


def test_os_check_fails(host):
    """Confirms install fails on unsupported OS"""
    host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    install_dependent_packages ${OS_CHECK_DEPS[@]}
    install_dependent_packages ${INSTALLER_DEPS[@]}
    cat <<EOT > /etc/os-release
ID=UnsupportedOS
VERSION_ID="2"
EOT
    """
    )
    detectOS = host.run(
        """t
    source /opt/pihole/basic-install.sh
    os_check
    """
    )
    expected_stdout = "Unsupported OS detected: UnsupportedOS"
    assert expected_stdout in detectOS.stdout


def test_os_check_passes(host):
    """Confirms OS meets the requirements"""
    host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    install_dependent_packages ${OS_CHECK_DEPS[@]}
    install_dependent_packages ${INSTALLER_DEPS[@]}
    """
    )
    detectOS = host.run(
        """
    source /opt/pihole/basic-install.sh
    os_check
    """
    )
    expected_stdout = "Supported OS detected"
    assert expected_stdout in detectOS.stdout


def test_package_manager_has_installer_deps(host):
    """Confirms OS is able to install the required packages for the installer"""
    mock_command("dialog", {"*": ("", "0")}, host)
    output = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    install_dependent_packages ${INSTALLER_DEPS[@]}
    """
    )

    assert "No package" not in output.stdout
    assert output.rc == 0


def test_package_manager_has_pihole_deps(host):
    """Confirms OS is able to install the required packages for Pi-hole"""
    mock_command("dialog", {"*": ("", "0")}, host)
    output = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    install_dependent_packages ${PIHOLE_DEPS[@]}
    """
    )

    assert "No package" not in output.stdout
    assert output.rc == 0


def test_package_manager_has_web_deps(host):
    """Confirms OS is able to install the required packages for web"""
    mock_command("dialog", {"*": ("", "0")}, host)
    output = host.run(
        """
    source /opt/pihole/basic-install.sh
    package_manager_detect
    install_dependent_packages ${PIHOLE_WEB_DEPS[@]}
    """
    )

    assert "No package" not in output.stdout
    assert output.rc == 0