import coloredlogs
import logging
import json
import subprocess
import os
import re
import shutil
from argparse import ArgumentParser
import sandbox.common.rest
import search.tools.devops.libs.nanny_services as nanny_services
import saas.tools.devops.refresh_configs as refresh_configs

_MODELS_ARC_NAME = "models.archive"
_MODELS_MODELS_NAME = "models"
_NANNY_BASE_PROD_SAMOHOD = "saas_refresh_3day_production_base_multilang"
_NANNY_BASE_PROD_SHMICK = "saas_shmick"
_SVN_CONFIG_DIR = "saas/static_configs/refresh"
_CONFIG_PATCHER = "rbtorrent:3014e6081a4645d3111c41a5a522161b0e52a974"

_PROFILES = {
    "dev_samohod": {"nanny": _NANNY_BASE_PROD_SAMOHOD},
    "dev_samohod_frozen": {"nanny": _NANNY_BASE_PROD_SAMOHOD},
    "dev_shmick": {"nanny": _NANNY_BASE_PROD_SHMICK},
    "dev_shmick_frozen": {"nanny": _NANNY_BASE_PROD_SHMICK}
}


def get_sandbox_resource_rbtorrent(service, sandbox_client, key_name):
    found_local_paths = []
    for resource in service['runtime_attrs']['content']['resources']['sandbox_files']:
        if resource['local_path'] == key_name:
            return (sandbox_client.resource[resource['resource_id']].read())['skynet_id']

        found_local_paths.append(resource['local_path'])

    raise Exception('Resource with local path {} is not found; available resources: {}.'.format(key_name, str(found_local_paths)))


def download_rbtorrent(torrent_id, name, data_dir):
    logging.info("Started loading %s from %s", name, torrent_id)
    subprocess.check_call(["sky", "get", "-d", data_dir, "-uwp", torrent_id])
    logging.info("...finished loading %s", name)


class NannyCache(object):
    def __init__(self, service_name):
        self.resources = []
        self.resources_cache = {}
        self.service = None
        self.service_name = service_name
        self.sandbox_client = None

    def _get_not_cached(self, key_name):
        if self.service is None:
            self.service = json.loads(nanny_services.get_service(self.service_name).text)

        if self.sandbox_client is None:
            self.sandbox_client = sandbox.common.rest.Client()

        return get_sandbox_resource_rbtorrent(self.service, self.sandbox_client, key_name)

    def get(self, key_name):
        if key_name not in self.resources_cache:
            logging.debug("%s resource is not found in the cache", key_name)
            self.resources_cache[key_name] = self._get_not_cached(key_name)
            logging.debug("%s resource written to the cache: %s", key_name, self.resources_cache[key_name])
        else:
            logging.debug("%s resource found in the cache: %s", key_name, self.resources_cache[key_name])

        return self.resources_cache[key_name]


def fetch_nanny_resource(filename, nanny_cache, options):
    data_dir = get_data_dir(options)
    output_path = os.path.join(data_dir, filename)
    if options.update_all or options.update_models or not os.path.exists(output_path):
        if os.path.exists(output_path):
            logging.debug("path '%s' already extists, REMOVE it", output_path)
            if os.path.isdir(output_path):
                shutil.rmtree(output_path)
            else:
                os.remove(output_path)
        download_rbtorrent(nanny_cache.get(filename), output_path, data_dir)
    else:
        logging.info("Skip already fetched path: %s", output_path)


def get_data_dir(options):
    if options.data_dir:
        return os.path.abspath(os.path.expanduser(options.data_dir))
    return os.getcwd()


def ensure_dir_exists(dir_path, force_recreate):
    if (os.path.exists(dir_path)):
        if force_recreate:
            logging.info("directory '%s' already extists, REMOVE it", dir_path)
            shutil.rmtree(dir_path)
            logging.debug("create new empty directory '%s'", dir_path)
            os.makedirs(dir_path)
        else:
            logging.debug("directory '%s' already extists, skip it", dir_path)
    else:
        logging.debug("directory '%s' does not exist, recreate it", dir_path)
        os.makedirs(dir_path)


def run_rtyserver(binary, port, options):
    cwd = os.getcwd()
    data_dir = get_data_dir(options)
    config_dir = os.path.join(data_dir, "config")

    required_dirs = {
        "LOG_PATH": "log",
        "JournalDir": "log",
        "STATE_ROOT": "state",
        "DETACH_DIRECTORY": "detach",
        "INDEX_DIRECTORY": "index",
        "RTINDEX_DIRECTORY": "rtindex",
        "MEDINDEX_DIRECTORY": "medindex"
    }
    v_params = {
        "BasePort": str(port),
        "CONFIG_PATH": config_dir,
        "MODELS_PATH": data_dir,
        "STATIC_DATA_DIRECTORY": os.path.join(data_dir, _MODELS_MODELS_NAME),
        "shardid": "0",
        "LOCATION": "SAS",
        "CPU_LIMIT": "5"
    }

    suffix = "-" + options.profile if options.profile != "common" else ""
    v_params_list = ["-E", os.path.join(config_dir, "environment" + suffix)]

    for key, value in v_params.items():
        v_params_list.append("-V")
        v_params_list.append("{}={}".format(key, value))

    for key, dirname in required_dirs.items():
        path = os.path.join(data_dir, dirname)
        ensure_dir_exists(path, options.update_all)
        v_params_list.append("-V")
        v_params_list.append("{}={}".format(key, path))

    call = [binary] + v_params_list + [os.path.join(config_dir, "rtyserver.conf-" + options.profile)]
    if not options.run:
        logging.info("rtyserver command line: %s", " ".join(call))
    else:
        logging.info("Starting process %s", " ".join(call))
        rtyserver_proc = subprocess.Popen(call, cwd=data_dir)
        rtyserver_proc.wait()


def generate_configs(options):
    data_dir = get_data_dir(options)
    tmp_dir = os.path.join(data_dir, "tmp")
    config_dir = os.path.join(data_dir, "config")
    force_update = options.update_all or options.update_config
    if os.path.exists(config_dir) and not force_update:
        logging.debug("config dir %s already exists, skip config generating", config_dir)
        return

    if options.config_tmpl and not os.path.exists(options.config_tmpl):
        logging.error("config templates directory '%s' does not exist", options.config_tmpl)
        return

    ensure_dir_exists(config_dir, force_update)
    ensure_dir_exists(tmp_dir, force_update)

    logging.info("Generating configs")
    config_patcher = os.path.join(tmp_dir, "config_patcher")
    if not os.path.exists(config_patcher):
        logging.info("Downloading config_patcher")
        download_rbtorrent(_CONFIG_PATCHER, config_patcher, tmp_dir)

    if options.config_tmpl:
        template_dir = options.config_tmpl
    else:
        template_dir = os.path.join(tmp_dir, "config_templates")
        checkout_config_templates(_SVN_CONFIG_DIR, template_dir)
    logging.debug("use config templates from %s", template_dir)

    generator = refresh_configs.Generator(
        yconf_patcher=config_patcher,
        input_dir=template_dir,
        output_dir=config_dir,
        environments=[options.profile],
        create_oxygen_rt=True,
        create_oxygen_med=True
    )
    generator.run()


def checkout_config_templates(arcadia_path, template_dir):
    svn_url = "svn+ssh://arcadia.yandex.ru/arc/trunk/arcadia/" + arcadia_path
    logging.info("Checking out config templates from Arcadia %s into %s", svn_url, template_dir)
    ensure_dir_exists(template_dir, True)
    subprocess.check_call(["svn", "checkout", svn_url, template_dir])
    logging.info("Checked out %s successfully", svn_url)


def main():
    profile_names = list(_PROFILES.keys())

    parser = ArgumentParser()
    parser.add_argument(
        "--profile",
        action="store",
        dest="profile",
        choices=profile_names,
        default=profile_names[0],
        help=f"profile name, default is {profile_names[0]}",
    )
    parser.add_argument(
        "--data-dir",
        action="store",
        dest="data_dir",
        default="",
        help="rtyserver's working directory",
    )
    parser.add_argument(
        "--run",
        action="store_true",
        dest="run",
        default=False,
        help="Run rtyserver",
    )
    parser.add_argument(
        "--update-all",
        action="store_true",
        dest="update_all",
        default=False,
        help="Force update all resources, remove and recreate index, log, state and detach directories",
    )
    parser.add_argument(
        "--update-models",
        action="store_true",
        dest="update_models",
        default=False,
        help="Force update models",
    )
    parser.add_argument(
        "--update-config",
        action="store_true",
        dest="update_config",
        default=False,
        help="Force update configs",
    )
    parser.add_argument(
        "--config-templates",
        action="store",
        dest="config_tmpl",
        default="",
        help="path to config templates (by default config templates are fetched from Arcadia)",
    )
    parser.add_argument(
        "args",
        nargs="*",
        action="store",
        help="[path to rtyserver binary] and [port]",
    )

    options = parser.parse_args()
    args = options.args

    if len(args) > 2:
        logging.error("Too many arguments.")
        parser.print_help()
        return

    if len(args) == 2:
        binary_path = args[0]
        binary_port = args[1]
        if not os.path.exists(binary_path):
            logging.error("rtyserver binary '%s' does not exist", binary_path)
            parser.print_help()
            return
    else:
        if options.run:
            logging.error("Cannot run rtyserver without path to rtyserver binary and port number")
            parser.print_help()
            return
        binary_path = '/path/to/rtyserver'
        binary_port = '$PORT'

    if options.profile not in _PROFILES:
        logging.error("Unknown profile name: %s", options.profile)
        return

    nanny_service = _PROFILES[options.profile]["nanny"]
    nanny_cache = NannyCache(nanny_service)
    fetch_nanny_resource(_MODELS_ARC_NAME, nanny_cache, options)
    fetch_nanny_resource(_MODELS_MODELS_NAME, nanny_cache, options)

    generate_configs(options)

    run_rtyserver(binary_path, binary_port, options)


if __name__ == "__main__":
    coloredlogs.install()
    coloredlogs.set_level(logging.DEBUG)

    main()
