#!/usr/bin/env python
#
# Copyright (c) 2011 Duo Security
# All Rights Reserved
#

# This import _must_ be first
import _fipscustomize # noqa: F401, isort:skip

import argparse
import os
import sys

import colorama
from twisted.internet import task
from twisted.logger import LogPublisher, textFileLogObserver

from duoauthproxy.lib import (
    config_provider,
    const,
    fips_manager,
    http,
    path_util,
    ssl_verify,
    util,
)
from duoauthproxy.lib.config_error import ConfigError, ConfigMissingError
from duoauthproxy.lib.config_provider import ConfigProvider
from duoauthproxy.lib.log import CONN_LOGGER, logger_container
from duoauthproxy.lib.log_observation import LeanLogObserver, create_log_file
from duoauthproxy.lib.validation import updates, validator

CONFIG_ERROR = 1
CONNECTIVITY_ERROR = 2
UNHANDLED_ERROR = 3


async def run_tool():
    parser = argparse.ArgumentParser(
        description="Duo Security Authentication Proxy Connectivity Troubleshooter. This tool will read your Authentication Proxy configuration file and test for any connectivity related issues"
    )
    parser.add_argument(
        "--explicit-connectivity-check",
        dest="explicit_connectivity_check",
        action="store_true",
    )
    parser.add_argument(
        "--no-explicit-connectivity-check",
        dest="explicit_connectivity_check",
        action="store_false",
    )
    parser.add_argument(
        "--startup", action="store_true", help=argparse.SUPPRESS
    )  # used for checks run on startup
    parser.add_argument("--config", dest="configuration_file")
    parser.set_defaults(explicit_connectivity_check=True)
    args = parser.parse_args()

    # Initialize colorama. This is needed by windows
    colorama.init()

    home_dir = path_util.get_home_dir()
    os.chdir(home_dir)
    try:
        if args.configuration_file:
            with open(args.configuration_file, "r") as fh:
                configuration = config_provider.ConfigProvider.from_ini_file(
                    fh, args.configuration_file
                )
        else:
            configuration = config_provider.get_config()
    except ConfigMissingError as e:
        raise SystemExit(CONFIG_ERROR) from e
    except ConfigError as e:
        print(
            'The following configuration error is preventing the tool from running. Please resolve it and try again: "{}"'.format(
                str(e)
            )
        )
        raise SystemExit(CONFIG_ERROR) from e

    main_config = configuration.get_main_section_config()

    http.setup_http_requestor(configuration)

    fips_mode = main_config.get_bool("fips_mode", False)
    if fips_mode:
        fips_manager.enable()

    log_dir = main_config.get_str("log_dir", "log")
    log_max_size = main_config.get_int("log_max_size", 10 * (1 << 20))
    log_max_files = main_config.get_int("log_max_files", 6)
    if log_max_files == 0:
        # we need to pass None explicitly if we want there to be no limit
        # 0 would just mean no logfiles would get kept...
        log_max_files = None

    log_filename = "connectivity_tool.log"
    log_file = create_log_file(
        log_filename, log_dir, log_max_size, log_max_files, None, None
    )
    conn_log_file_observer = textFileLogObserver(log_file)
    stdout_observer = LeanLogObserver(sys.stdout)

    connectivity_publisher = LogPublisher()
    connectivity_publisher.addObserver(conn_log_file_observer)
    connectivity_publisher.addObserver(stdout_observer)
    logger_container.add_logger(CONN_LOGGER, connectivity_publisher)
    log = logger_container.get_logger(CONN_LOGGER)

    # Only check the config for test_connectivity_on_startup if we're actually
    # running this on startup as opposed to directly running the script
    if (
        args.startup and main_config.get_bool("test_connectivity_on_startup", False)
    ) or (not args.startup and args.explicit_connectivity_check):
        print(
            "Running The Duo Authentication Proxy Connectivity Tool. This may take several minutes..."
        )
        results = await validator.check_config_and_connectivity(configuration)
        results.output_results(log)
        print(
            "\nThe results have also been logged in {0}".format(
                os.path.join(home_dir, log_dir, log_filename)
            )
        )
        if results.exception:
            raise SystemExit(UNHANDLED_ERROR)
    else:
        print("Running a validation of your configuration...")
        results = await validator.check_config(configuration)
        results.output_config_results(log)
        if results.exception:
            results.log_exception(log)
            raise SystemExit(UNHANDLED_ERROR)
        else:
            results.output_summary(log)
        print(
            "\nThe results have also been logged in {0}".format(
                os.path.join(home_dir, log_dir, log_filename)
            )
        )

    # Running updates check at the end of connectivity tool
    print("\nChecking updates for Duo Authentication Proxy...")
    await updates.check_updates(configuration, log)

    if not results.config_sections_successful():
        # Use lighter colors on windows as the normal green and red are too dark
        with util.set_stdout_color("red"):
            log.error("Configuration validation was not successful")
        raise SystemExit(CONFIG_ERROR)

    # Only check connectivity result if config results are good.
    if not results.skipped_connectivity_tests and not results.all_sections_successful():
        with util.set_stdout_color("red"):
            log.error("Connectivity validation was not successful")
            raise SystemExit(CONNECTIVITY_ERROR)

    return "Done"


@task.react
async def main(_reactor):
    try:
        await run_tool()
    except SystemExit as e:
        # A SystemExit exception from the tool means the tool ran properly
        # and translated the result to an exit code. Just re-raise it.
        raise e
    except Exception as e:
        # Any other exception type was unexpected and unhandled. Raise a
        # SystemExit so we don't exit with a 0
        print(
            "An exception prevented the connectivity tool from running: {}".format(
                str(e)
            )
        )
        raise SystemExit(UNHANDLED_ERROR) from e
