'''
SPDX-License-Identifier: Apache-2.0
Copyright 2017 Massachusetts Institute of Technology.
'''
import os
import os.path
import configparser
from typing import Optional

# resources in importlib is a Python 3.7 feature, so we disable fallback support if we cannot import this module
try:
    from importlib import resources
    _CONFIG_FALLBACK = True
except ImportError:
    _CONFIG_FALLBACK = False

import yaml
try:
    from yaml import CSafeLoader as SafeLoader
except ImportError:
    from yaml import SafeLoader
from yaml.reader import ReaderError

from keylime import json


def convert(data):
    if isinstance(data, bytes):
        return data.decode()
    if isinstance(data, dict):
        return dict(map(convert, data.items()))
    if isinstance(data, tuple):
        return tuple(map(convert, data))
    if isinstance(data, list):
        return list(map(convert, data))
    return data


def environ_bool(env_name, default):
    val = os.getenv(env_name, 'default').lower()
    if val in ["on", "true", "1"]:
        return True
    if val in ["off", "false", "0"]:
        return False
    if val == "default":
        return default
    raise ValueError(
        "Environment variable %s set to invalid value "
        "%s (use either on/true/1 or off/false/0)" %
        (env_name, val))


# SET STUB_TPM TO True TO ALLOW ALL TPM Operations to be stubbed out
# If STUB_TPM=True, TPM_CANNED_VALUES_PATH file must be provided (canned inputs)
# Canned input values can be generated by running with STUB_TPM=False and
#   specifying a TPM_CANNED_VALUES_PATH filename
STUB_TPM = False
TPM_CANNED_VALUES_PATH = None

# SET TO TRUE TO STUB A VTPM
STUB_VTPM = False
# force stub tpm if vtpm true
if STUB_VTPM:
    STUB_TPM = True

# Enable TPM benchmarking (output timing data to given file)
TPM_BENCHMARK_PATH = None

# set to False to enable keylime to run from the CWD and not require
# root access.  for testing purposes only
# all processes will log to the CWD in keylime-all.log
REQUIRE_ROOT = environ_bool('KEYLIME_REQUIRE_ROOT', True)

# enable printing of keys and other info for debug purposes
INSECURE_DEBUG = False

# allow the emuatlor to not have an ekcert even if check ekcert is true
DISABLE_EK_CERT_CHECK_EMULATOR = False

# stub out IMA functionality
STUB_IMA = False

if STUB_TPM:
    STUB_IMA = True

# allow testing mode
TEST_MODE = os.getenv('KEYLIME_TEST', 'False')
if TEST_MODE.upper() == 'TRUE':
    print("WARNING: running keylime in testing mode.\nkeylime will not run as root and ekcert checking for the TPM emulator is disabled")
    REQUIRE_ROOT = False
    DISABLE_EK_CERT_CHECK_EMULATOR = True

# whether to use tpmfs or not
MOUNT_SECURE = True

# load in JSON canned values if we're in stub mode (and JSON file given)
TPM_CANNED_VALUES = None
if STUB_TPM and TPM_CANNED_VALUES_PATH is not None:
    with open(TPM_CANNED_VALUES_PATH, "rb") as can:
        print("WARNING: using canned values in stub mode from file '%s'" %
              (TPM_CANNED_VALUES_PATH))
        # Read in JSON and strip trailing extraneous commas
        jsonInTxt = can.read().rstrip(',\r\n')
        # Saved JSON is missing surrounding braces, so add them here
        TPM_CANNED_VALUES = json.loads('{' + jsonInTxt + '}')
elif STUB_TPM:
    raise Exception(
        'STUB_TPM=True but required TPM_CANNED_VALUES_PATH not provided!')


if not REQUIRE_ROOT:
    MOUNT_SECURE = False

if not REQUIRE_ROOT:
    print("WARNING: running without root access")


# Config files can be merged together, reading from the system to the
# user.
CONFIG_FILES = [
    os.path.expanduser("~/.config/keylime.conf"), "/etc/keylime.conf", "/usr/etc/keylime.conf"
]
if "KEYLIME_CONFIG" in os.environ:
    CONFIG_FILES.insert(0, os.environ["KEYLIME_CONFIG"])


def get_config():
    """Read configuration files and merge them together."""
    if not getattr(get_config, "config", None):
        # Use RawConfigParser, so we can also use it as the logging config
        config = configparser.RawConfigParser()
        # TODO - use logger and be sure that all variables have a
        # propper default, and the sections are initialized
        if not any(os.path.exists(c) for c in CONFIG_FILES):
            print(f"Config file not found in {CONFIG_FILES}. Please set "
                  f"environment variable KEYLIME_CONFIG or see {__file__} "
                  "for more details.")
            if _CONFIG_FALLBACK:
                print("Falling back on package provided configuration")
                file = resources.files(__package__).joinpath("keylime.conf")
                config.read_string(file.read_text("utf-8"))
        else:
            # Validate that at least one config file is present
            config_files = config.read(CONFIG_FILES)
            # TODO - use the logger
            print(f"Reading configuration from {config_files}")
        get_config.config = config
    return get_config.config


# Re-export some utility functions
get = get_config().get
getint = get_config().getint
getboolean = get_config().getboolean
getfloat = get_config().getfloat
has_option = get_config().has_option

if not REQUIRE_ROOT:
    DEFAULT_WORK_DIR = os.path.abspath(".")
else:
    DEFAULT_WORK_DIR = '/var/lib/keylime'
WORK_DIR = os.getenv('KEYLIME_DIR', DEFAULT_WORK_DIR)

CA_WORK_DIR = '%s/ca/' % WORK_DIR


def yaml_to_dict(arry, add_newlines=True, logger=None) -> Optional[dict]:
    arry = convert(arry)
    sep = "\n" if add_newlines else ""
    try:
        return yaml.load(sep.join(arry), Loader=SafeLoader)
    except ReaderError as err:
        if logger is not None:
            logger.warning("Could not load yaml as dict: %s", str(err))
    return None


if STUB_IMA:
    IMA_ML = '../scripts/ima/ascii_runtime_measurements'
else:
    IMA_ML = '/sys/kernel/security/ima/ascii_runtime_measurements'

IMA_PCR = 10

# measured boot addons
# PCRs 0-7: BIOS & UEFI
# PCRs 8-9: bootloader (grub)
# PCR 14: MokList, MokListX, and MokSBState
MEASUREDBOOT_PCRS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]
MEASUREDBOOT_ML = '/sys/kernel/security/tpm0/binary_bios_measurements'
MEASUREDBOOT_IMPORTS = get_config().get('cloud_verifier', 'measured_boot_imports', fallback='').split(',')
MEASUREDBOOT_POLICYNAME = get_config().get('cloud_verifier', 'measured_boot_policy_name', fallback='accept-all')

LIBEFIVAR="libefivar.so" # formerly "/usr/lib/x86_64-linux-gnu/libefivar.so"

# this is where data will be bound to a quote, MUST BE RESETABLE!
TPM_DATA_PCR = 16

# the size of the bootstrap key for AES-GCM 256bit
BOOTSTRAP_KEY_SIZE = 32

# choose between cfssl or openssl for creating CA certificates
CA_IMPL = get_config().get('general', 'ca_implementation', fallback='openssl')

CRL_PORT = 38080
