import os
import sys
import json
import getpass
import requests
import copy
import re
import zipfile
import tempfile
import numpy as np
import time
import tomli
import tomli_w
from tabulate import tabulate
from urllib.parse import urlparse
from packaging.version import Version

# push
import openmdao.utils.hooks as hooks
from openmdao.utils.file_utils import _load_and_exec

from openmdao.utils.webview import webview
from openmdao.api import IndepVarComp

from openmdao import __version__ as OPENMDAO_VERSION

from whatsopt.convert_utils import convert_sqlite_to_csv

from whatsopt.logging import log, info, warn, error, debug
from whatsopt.utils import (
    FRAMEWORK_GEMSEO,
    FRAMEWORK_OPENMDAO,
    MODE_PACKAGE,
    MODE_PLAIN,
    extract_remote_name,
    is_analysis_user_file,
    is_based_on,
    is_framework_switch,
    is_package_mode,
    is_run_script_file,
    is_test_file,
    is_user_file,
    get_analysis_id,
    get_whatsopt_url,
    move_files,
    save_state,
)
from whatsopt.upload_utils import (
    load_from_csv,
    load_from_sqlite,
    load_from_hdf5,
    print_cases,
)
from whatsopt.push_utils import (
    find_indep_var_name,
    problem_pyfile,
    to_camelcase,
)
from whatsopt.push_command import PushCommand
from whatsopt.show_utils import generate_xdsm_html

from whatsopt import __version__

WHATSOPT_DIRNAME = os.path.join(os.path.expanduser("~"), ".whatsopt")
API_KEY_FILENAME = os.path.join(WHATSOPT_DIRNAME, "api_key")
URL_FILENAME = os.path.join(WHATSOPT_DIRNAME, "url")
REMOTES_FILENAME = os.path.join(WHATSOPT_DIRNAME, "remotes")

EXTRANET_SERVER_URL = "https://ether.onera.fr/whatsopt"


class WhatsOptImportMdaError(Exception):
    pass


class AnalysisPushedException(Exception):
    def __init__(self, xdsm):
        self.xdsm = xdsm


class WhatsOpt:
    def __init__(self, url=None, api_key=None, login=None):
        self._remotes = self._read_remotes()

        remote = self._remotes.get(url)
        debug(f"remote={remote}")
        if remote:
            self._url = remote["url"]
            self._api_key = api_key if api_key else remote["api_key"]
        else:
            debug(f"url={url} logged?={self.is_logged()}")
            if url:
                self._url = url.strip("/")
                current_url = self._read_url()
                if current_url and self._url != current_url:
                    self.logout(echo=False)
            elif self.is_logged():
                self._url = self._read_url()
            else:
                self._url = EXTRANET_SERVER_URL

            if api_key:
                self._api_key = api_key
            else:
                self._api_key = self._read_api_key()

        # config session object
        self.session = requests.Session()
        urlinfos = urlparse(self._url)
        self.session.trust_env = re.match(r"\w+.onera\.fr", urlinfos.netloc)
        self.headers = {}

    @property
    def url(self):
        return self._url

    @property
    def api_key(self):
        return self._api_key

    def endpoint(self, path):
        return self._url + path

    @staticmethod
    def _read_remotes():
        remotes = {}
        if os.path.exists(REMOTES_FILENAME):
            with open(REMOTES_FILENAME, "rb") as f:
                remotes = tomli.load(f)
        return remotes

    @staticmethod
    def _write_remotes(remotes):
        with open(REMOTES_FILENAME, "wb") as f:
            tomli_w.dump(remotes, f)

    def _ask_api_key(self):
        log("You have to set your API key.")
        log("You can get it in your profile page on WhatsOpt (%s)." % self.url)
        info(
            "Please, copy/paste your API key below then hit return (characters are hidden)."
        )
        api_key = getpass.getpass(prompt="Your API key: ")
        return api_key

    @staticmethod
    def is_logged():
        # Defensive programming: test on api_key should be enough but...
        return os.path.exists(URL_FILENAME) and os.path.exists(API_KEY_FILENAME)

    @staticmethod
    def _read_url():
        if os.path.exists(URL_FILENAME):
            with open(URL_FILENAME, "r") as f:
                return f.read()
        else:
            return None

    @staticmethod
    def _read_api_key():
        if os.path.exists(API_KEY_FILENAME):
            with open(API_KEY_FILENAME, "r") as f:
                return f.read()
        else:
            return None

    def _write_login_infos(self):
        if not os.path.exists(WHATSOPT_DIRNAME):
            os.makedirs(WHATSOPT_DIRNAME)
        with open(API_KEY_FILENAME, "w") as f:
            f.write(self.api_key)
        with open(URL_FILENAME, "w") as f:
            f.write(self.url)

    def login(self, echo=False, retry=True):
        debug(f"login(api_key={self.api_key}, echo={echo})")

        # Check login with remote name
        if not (
            self.url.startswith("http://") or self.url.startswith("https://")
        ) and not self._remotes.get(self.url):
            error(f"Unknown remote server '{self.url}'")
            WhatsOpt.list_remotes()
            sys.exit(-1)

        if self._api_key:
            # api key is provided
            pass
        elif self.is_logged():
            # check if logged in
            self._api_key = self._read_api_key()
        else:
            # check if known remote
            for _, v in self._remotes.items():
                if self.url == v["url"]:
                    self._api_key = v["api_key"]
                    break
            # ask for API key otherwise
            debug("Ask for API key")
            self._api_key = self._ask_api_key() if not self._api_key else self._api_key

        debug(f"url={self.url}, api_key={self.api_key}")
        ok = self._test_connection()
        debug(f"ok={ok}")
        if not ok and retry:
            # try to log again
            # log out silently as one may be logged on another server
            self.logout(echo=False)
            self._remotes = {
                k: v for k, v in self._remotes.items() if v["url"] != self.url
            }
            # save url again as is has been wipe out by logout
            with open(URL_FILENAME, "w") as f:
                f.write(self._url)
            return self.login(retry=False)

        # make remote info
        if ok:
            self._write_login_infos()
            remote_name = extract_remote_name(self.url)
            if remote_name:
                self._remotes[remote_name] = {"url": self.url, "api_key": self.api_key}

        debug(self._remotes)
        self._write_remotes(self._remotes)

        if not ok:
            error("Login to WhatsOpt ({}) failed.".format(self.url))
            log("")
            warn("You are not connected.")
            info("  use `wop login <url>` to connect to a remote WhatsOpt server")
            info(
                "  use `wop login <name>` to connect to a known remote WhatsOpt server"
            )
            self.list_remotes()

            sys.exit(-1)

        if echo:
            info(f"Successfully logged in to remote WhatsOpt {self.url}")
            log("")
        return self

    @staticmethod
    def logout(list=None, all=None, remote=None, echo=True):
        if list:
            WhatsOpt.list_remotes()
        elif all:
            WhatsOpt._write_remotes({})
            if echo:
                info("Sucessfully logged out from all WhatsOpt remotes")
        elif remote:
            remotes = WhatsOpt._read_remotes()
            if remotes.get(remote):
                del remotes[remote]
                WhatsOpt._write_remotes(remotes)
                if echo:
                    info(f"Sucessfully logged out from remote WhatsOpt {remote}")
        else:
            if os.path.exists(API_KEY_FILENAME):
                os.remove(API_KEY_FILENAME)
            url = WhatsOpt._read_url()
            if url:
                os.remove(URL_FILENAME)
            if echo:
                if url:
                    info(f"Sucessfully logged out from remote WhatsOpt {url}")
                else:
                    info("Not connected. You may want to connect to:")
                    WhatsOpt.list_remotes()

        if echo:
            log("")

    @staticmethod
    def list_remotes():
        remotes = WhatsOpt._read_remotes()
        headers = ["name", "url"]
        data = []
        for name, rem in remotes.items():
            data.append([name, rem["url"]])
        info("Known remote servers")
        log(tabulate(data, headers))

    def list_analyses(self, all=False, project_query=None):
        param = ""
        if all:
            param = "?all=true"
        elif project_query:
            param = "?design_project_query={}".format(project_query)
        url = self.endpoint("/api/v1/analyses" + param)
        resp = self.session.get(url, headers=self.headers)
        if resp.ok:
            mdas = resp.json()
            headers = ["id", "name", "created at"]
            data = []
            for mda in mdas:
                date = mda.get("created_at", None)
                data.append([mda["id"], mda["name"], date])
            log(tabulate(data, headers))
            log("")
        else:
            WhatsOpt.check_http_error(resp)

    def is_connected(self):
        return self._test_connection()

    def get_status(self):
        connected = self.is_connected()
        whatsopt_url = get_whatsopt_url() or self.url
        if connected:
            info("You are logged in {}".format(self.url))
        else:
            info("You are not connected.")
        mda_id = None
        try:
            mda_id = get_analysis_id()
        except ValueError as err:
            warn(str(err))
        if mda_id:
            if connected and whatsopt_url == self.url:
                info("Found local analysis code (id=#{})".format(mda_id))
                # connected to the right server from which the analysis was pulled
                url = self.endpoint("/api/v1/analyses/{}".format(mda_id))
                resp = self.session.get(url, headers=self.headers)

                if resp.ok:
                    mda = resp.json()
                    if is_based_on(FRAMEWORK_GEMSEO):
                        mda["framework"] = "GEMSEO"
                    elif is_based_on(FRAMEWORK_OPENMDAO):
                        mda["framework"] = "OpenMDAO"
                    else:  # should not happen
                        raise ValueError(
                            "No framework detected. Check your *_base.py files."
                        )
                    headers = ["id", "name", "created_at", "owner_email", "framework"]
                    data = [[mda[k] for k in headers]]
                    log(tabulate(data, headers))
                else:
                    error("Analysis not found on the server anymore (probably deleted)")
                    log(
                        "  (use 'wop push <analysis.py>' to push from an OpenMDAO code to the server)"
                    )
            else:
                info(
                    "Found local analysis code (id=#{}) pulled from {}".format(
                        mda_id, whatsopt_url
                    )
                )
                if connected:
                    # connected to another server with a pulled analysis
                    warn("You are connected to a different server")
                    log(
                        "  (use 'wop push <analysis.py>' to push from the local "
                        f"analysis code to the server {self.url})"
                    )
                    log(
                        f"  (use 'wop logout' and 'wop login {whatsopt_url}' "
                        "to log in to the original server)"
                    )
                else:
                    log(f"  (use 'wop login {whatsopt_url}' command to log in)")
        else:
            info("No local analysis found")
            if connected:
                log(
                    "  (use 'wop list' and 'wop pull <id>' to retrieve an existing analysis)\n"
                    "  (use 'wop push <analysis.py>' to push from the local OpenMDAO code to the server)"
                )
        log("")

    def push_component_cmd(self, py_filename, component, options):
        with problem_pyfile(py_filename, component) as pyf:
            if options["--dry-run"]:
                with open(pyf, "r") as pbf:
                    print(pbf.read())
                    sys.exit()
            self.push_mda_cmd(pyf, options)

    def push_mda_cmd(self, py_filename, options):
        def push_mda(prob):
            name = options["--name"]
            pbname = prob.model.__class__.__name__
            if name and pbname != name:
                info("Analysis %s skipped" % pbname)
                # do not exit seeking for another problem (ie analysis)
            else:
                options["--pyfilename"] = py_filename
                xdsm = self.push_mda(prob, options)
                if options.get("--xdsm"):  # show command
                    # required to interrupt pb execution
                    raise AnalysisPushedException(xdsm=xdsm)
                else:
                    sys.exit()

        hooks.use_hooks = True
        hooks._register_hook("final_setup", "Problem", post=push_mda)
        _load_and_exec(py_filename, [])
        return push_mda

    def push_mda(self, problem, options):
        scalar = options.get("--scalar")
        depth = options.get("--depth")
        push_cmd = PushCommand(problem, depth, scalar)

        mda_attrs = push_cmd.get_mda_attributes(
            problem.model, push_cmd.tree, use_depth=True
        )

        if mda_attrs["name"] == "Group" and options.get("--pyfilename"):
            mda_attrs["name"] = os.path.splitext(
                to_camelcase(os.path.basename(options.get("--pyfilename")))
            )[0]

        if options["--dry-run"]:
            log(json.dumps(mda_attrs, indent=2))
        else:
            suffix = ""
            if options.get("--xdsm"):
                suffix = ".xdsm"
            url = self.endpoint("/api/v1/analyses{}".format(suffix))
            resp = self.session.post(
                url, headers=self.headers, json={"analysis": mda_attrs}
            )
            WhatsOpt.check_http_error(resp)
            log("Analysis %s pushed" % mda_attrs["name"])
            return resp.json()

    def push_json(self, filename):
        with open(filename, "rb") as f:
            attrs = json.load(f)
        if "analyses_attributes" in attrs:  # project detection
            url = self.endpoint("/api/v1/design_projects")
            key = "Project"
        else:
            url = self.endpoint("/api/v1/analyses")
            key = "Analysis"
        params = {}
        params[key.lower()] = attrs
        resp = self.session.post(url, headers=self.headers, json=params)
        WhatsOpt.check_http_error(resp)
        log("{} {} pushed".format(key, attrs["name"]))

    def pull_mda(self, mda_id, options={}, msg=None, info_keep_run_ops=True):
        if not msg:
            msg = "Analysis %s pulled" % mda_id

        framework = FRAMEWORK_OPENMDAO
        if options.get("--gemseo"):
            framework = FRAMEWORK_GEMSEO

        param = ""
        if options.get("--run-ops"):
            param += "&with_runops=true"
        if options.get("--server"):
            if framework == FRAMEWORK_OPENMDAO:
                param += "&with_server=true"
            else:
                warn(
                    "Can not generate server with GEMSEO framework. --server is ignored"
                )
        if options.get("--egmdo"):
            if framework == FRAMEWORK_OPENMDAO:
                param += "&with_egmdo=true"
            else:
                warn("Can not generate EGMDO with GEMSEO framework. --egmdo is ignored")
        if options.get("--test-units"):
            if framework == FRAMEWORK_OPENMDAO:
                param += "&with_unittests=true"
            else:
                warn(
                    "Can not generate tests with GEMSEO framework. --test-units is ignored"
                )
        if param:
            param = "?" + param[1:]

        format_query = framework
        if options.get("--package"):
            format_query += "_pkg"

        url = self.endpoint(
            ("/api/v1/analyses/{}/exports/new.{}{}".format(mda_id, format_query, param))
        )
        resp = self.session.get(url, headers=self.headers, stream=True)
        WhatsOpt.check_http_error(resp)
        name = None
        with tempfile.NamedTemporaryFile(suffix=".zip", mode="wb", delete=False) as fd:
            for chunk in resp.iter_content(chunk_size=128):
                fd.write(chunk)
            name = fd.name
        zipf = zipfile.ZipFile(name, "r")
        tempdir = tempfile.mkdtemp(suffix="wop", dir=tempfile.tempdir)
        zipf.extractall(tempdir)
        filenames = zipf.namelist()
        zipf.close()
        file_to_move = {}
        if options.get("--dry-run"):
            # cmd = "Pull"
            # if options.get("--update"):
            #     cmd = "Update"
            info(
                "***************************************************\n"
                "* DRY RUN mode (actions are listed but not done) *\n"
                "***************************************************"
            )

        # Special case: When framework switch we have to update
        # user analysis main file which name depends on package name
        framework_switch = is_framework_switch(framework)
        if framework_switch:
            url = self.endpoint(f"/api/v1/analyses/{mda_id}/openmdao_impl")
            resp = self.session.get(url, headers=self.headers, stream=True)
            WhatsOpt.check_http_error(resp)
            mda_name = resp.json()["packaging"]["package_name"]
        else:
            mda_name = ""

        for f in filenames:
            file_to = f
            file_to_move[file_to] = True
            if os.path.exists(file_to):
                if options.get("--force"):
                    log(f"Update {file_to}")
                    if options.get("--dry-run"):
                        file_to_move[file_to] = False
                    else:
                        os.remove(file_to)
                elif options.get("--update"):
                    if is_run_script_file(f) and not options.get("--run-ops"):
                        if info_keep_run_ops:
                            info(f"Keep existing {file_to} (use -r to override)")
                        file_to_move[file_to] = False
                        continue
                    if is_test_file(f) and not options.get("--test-units"):
                        file_to_move[file_to] = False
                        continue
                    if is_user_file(f):
                        file_to_move[file_to] = False
                        # Have to update user analysis main file when switching frameworks
                        if is_framework_switch and is_analysis_user_file(mda_name, f):
                            file_to_move[file_to] = True
                        else:
                            continue
                    log(f"Update {file_to}")
                    if not options.get("--dry-run"):
                        os.remove(file_to)
                else:
                    warn(
                        f"File {file_to} in the way: remove it or use --force to override"
                    )
                    file_to_move[file_to] = False
            else:
                if options.get("--force"):
                    log(f"Pull {file_to}")
                    if options.get("--dry-run"):
                        file_to_move[file_to] = False
                elif options.get("--update") and (
                    (is_run_script_file(f) and not options.get("--run-ops"))
                    or (is_test_file(f) and not options.get("--test-units"))
                    or is_user_file(f)
                ):
                    file_to_move[file_to] = False
                else:
                    log(f"Pull {file_to}")

        if not options.get("--dry-run"):
            move_files(file_to_move, tempdir)
            state = {
                "whatsopt_url": self._url,
                "analysis_id": mda_id,
                "framework": framework,
                "pull_mode": MODE_PACKAGE if options.get("--package") else MODE_PLAIN,
            }
            save_state(state)
            info(msg)

    def pull_mda_json(self, mda_id):
        url = self.endpoint(f"/api/v1/analyses/{mda_id}.wopjson")
        resp = self.session.get(url, headers=self.headers, stream=True)
        WhatsOpt.check_http_error(resp)
        print(json.dumps(resp.json()))

    def pull_project_json(self, project_id):
        url = self.endpoint(f"/api/v1/design_projects/{project_id}.wopjson")
        resp = self.session.get(url, headers=self.headers, stream=True)
        WhatsOpt.check_http_error(resp)
        print(json.dumps(resp.json()))

    def update_mda(self, analysis_id=None, options={}, info_keep_run_ops=True):
        mda_id = analysis_id or get_analysis_id()
        if mda_id and not analysis_id:
            url = get_whatsopt_url()
            if url != self.url:
                warn(
                    f"You want to update code pulled from {url} while you are logged in {self.url}."
                )
                info(
                    f"  use 'wop login {url}' and retry to update using the original remote."
                )
                info(
                    f"  use 'wop update -a <id>' to update using analysis #<id> from {self.url}."
                )
                sys.exit(-1)
        if mda_id is None:
            error(
                f"Unknown analysis with id=#{mda_id} (maybe use wop pull <analysis-id>)"
            )
            sys.exit(-1)
        # keep options unchanged, work on a copy
        opts = copy.deepcopy(options)
        # sanity checks
        if not (is_based_on(FRAMEWORK_OPENMDAO) or is_based_on(FRAMEWORK_GEMSEO)):
            error("No framework detected. Check your *_base.py files.")
            sys.exit(-1)
        if opts["--openmdao"] and opts["--gemseo"]:
            error("Please choose either --openmdao or --gemseo.")
            sys.exit(-1)
        opts.update(
            {
                "--base": True,
                "--update": True,
                "--gemseo": opts["--gemseo"]
                or (not opts["--openmdao"] and is_based_on(FRAMEWORK_GEMSEO)),
                "--openmdao": opts["--openmdao"]
                or (not opts["--gemseo"] and is_based_on(FRAMEWORK_OPENMDAO)),
                "--package": is_package_mode(),
            }
        )
        self.pull_mda(
            mda_id, opts, "Analysis #{} updated".format(mda_id), info_keep_run_ops
        )

    def show_mda(self, analysis_id, pbfile, name, outfile, batch, depth):
        options = {
            "--xdsm": True,
            "--name": name,
            "--dry-run": False,
            "--depth": depth,
        }
        xdsm = None
        if pbfile:
            start = time.time()
            try:
                info("XDSM info retrieval...")
                self.push_mda_cmd(pbfile, options)
            except AnalysisPushedException as pushed:
                xdsm = pushed.xdsm
            end = time.time()
            log("Retrieved in {:.2f}s".format(end - start))
            source = os.path.basename(pbfile)
        else:
            mda_id = analysis_id or get_analysis_id()
            if mda_id is None:
                error(
                    "Unknown analysis with id={} (maybe use wop pull <analysis-id>)".format(
                        mda_id
                    )
                )
                sys.exit(-1)
            url = self.endpoint("/api/v1/analyses/{}.xdsm".format(mda_id))
            resp = self.session.get(url, headers=self.headers)
            WhatsOpt.check_http_error(resp)
            xdsm = resp.json()
            source = f"{mda_id}@{self._url}"

        info("XDSM building...")
        generate_xdsm_html(source, xdsm, outfile)
        if pbfile:
            log("XDSM of analysis from {} generated in {}".format(pbfile, outfile))
        else:
            log("XDSM of analysis {} generated in {}".format(mda_id, outfile))
        if not batch:
            webview(outfile)

    def upload(
        self,
        filename,
        driver_kind=None,
        analysis_id=None,
        operation_id=None,
        dry_run=False,
        outvar_count=1,
        only_success=False,
        parallel=False,
    ):
        from socket import gethostname

        mda_id = get_analysis_id() if not analysis_id else analysis_id
        # Test sqlite files generated with MPI
        _, extension = os.path.splitext(filename)
        parallel_sqlite = re.match(r"\.sqlite_\d+$", extension)

        name = cases = statuses = None
        if not os.path.exists(filename):
            error(f"File not found ({filename})")
            sys.exit(-1)
        if os.path.basename(filename) == "mda_init.py":
            self.upload_vars_init_cmd(
                filename, {"--dry-run": dry_run, "--analysis-id": mda_id}
            )
        elif filename.endswith(".csv"):
            name, cases, statuses = load_from_csv(filename)
        elif filename.endswith(".sqlite") or (parallel_sqlite and parallel):
            name, cases, statuses = load_from_sqlite(filename, parallel)
        elif filename.endswith(".hdf5"):
            name, cases, statuses = load_from_hdf5(filename)
        else:
            error(
                f"Can not upload file {filename}: extension not recognized"
                " (should be either .csv, .sqlite or .hdf5)"
            )
            sys.exit(-1)

        if only_success:
            for c in cases:
                c["values"] = [
                    val for i, val in enumerate(c["values"]) if statuses[i] > 0
                ]
            statuses = [1 for s in statuses if s > 0]

        for c in cases:
            c["values"] = np.nan_to_num(np.array(c["values"])).tolist()

        if dry_run:
            print_cases(cases, statuses)
            sys.exit()

        resp = None
        if operation_id:
            url = self.endpoint(("/api/v1/operations/%s") % operation_id)
            operation_params = {"cases": cases}
            resp = self.session.patch(
                url, headers=self.headers, json={"operation": operation_params}
            )
        else:
            if mda_id:
                url = self.endpoint(("/api/v1/analyses/%s/operations") % mda_id)
            else:
                url = self.endpoint("/api/v1/operations")
            if driver_kind:
                driver = "user_{}_algo".format(driver_kind)
            else:
                # suppose name well-formed <lib>-<doe|optimizer|screening>-<algoname>
                # otherwise it will default to doe
                m = re.match(r"(\w+)_(doe|optimizer|screening)_(\w+)", name.lower())
                if m:
                    driver = name.lower()
                else:
                    driver = "user_data_uploading"
            operation_params = {
                "name": name,
                "driver": driver,
                "host": gethostname(),
                "cases": cases,
                "success": statuses,
            }
            params = {"operation": operation_params}
            if outvar_count > 0 and outvar_count < len(cases):
                params["outvar_count_hint"] = outvar_count
            resp = self.session.post(url, headers=self.headers, json=params)
        WhatsOpt.check_http_error(resp)
        log("Results data from {} uploaded with driver {}".format(filename, driver))
        if mda_id:
            log(f"attached to analysis #{mda_id}")

    def upload_vars_init_cmd(self, py_filename, options):
        def upload_vars_init(prob):
            self.upload_vars_init(prob, options)
            sys.exit()

        d = os.path.dirname(py_filename)
        run_mda_filename = os.path.join(d, "run_mda.py")
        if not os.path.exists(run_mda_filename):
            error(f"Can not get analysis init: script {run_mda_filename} not found.")
        hooks.use_hooks = True
        hooks._register_hook("final_setup", "Problem", post=upload_vars_init)
        _load_and_exec(run_mda_filename, [])

    def upload_vars_init(self, problem, options):
        mda_id = get_analysis_id() if get_analysis_id() else options["--analysis-id"]
        if mda_id is None:
            error("Unknown analysis with id={}".format(mda_id))
            sys.exit(-1)
        parameters = []
        headers = ["variable", "init value"]
        data = []
        for s in problem.model._subsystems_myproc:
            if isinstance(s, IndepVarComp):
                for absname in s._var_abs2meta["output"]:
                    name = find_indep_var_name(problem, absname)
                    value = None
                    if Version(OPENMDAO_VERSION) < Version("3.39"):
                        value = s._outputs._views[absname][0]
                    else:
                        value = s._outputs.get_val(absname, flat=False)
                    if isinstance(value, np.ndarray):
                        value = str(value.tolist())
                    parameters.append({"varname": name, "value": value})
        data = [[p["varname"], p["value"]] for p in parameters]
        params = {"parameterization": {"parameters": parameters}}
        log(tabulate(data, headers))
        if not options["--dry-run"]:
            url = self.endpoint(f"/api/v1/analyses/{mda_id}/parameterization")
            resp = self.session.put(url, headers=self.headers, json=params)
            WhatsOpt.check_http_error(resp)
            log("Variables init values uploaded")

    def check_versions(self):
        url = self.endpoint("/api/v1/versioning")
        resp = self.session.get(url, headers=self.headers)
        WhatsOpt.check_http_error(resp)
        version = resp.json()
        log("WhatsOpt {} requires wop {}".format(version["whatsopt"], version["wop"]))
        log(f"You are using wop {__version__}")

    @staticmethod
    def serve(port):
        try:
            import thrift  # noqa: F401
        except ImportError:
            error(
                "Apache Thrift is not installed. You can install it with : 'pip install thrift'"
            )
            exit(-1)
        try:
            import os
            import sys

            # insert current dir first as another run_server exists under whatsopt/services
            sys.path.insert(0, os.getcwd())
            from run_server import run_server
        except ImportError as err:
            print(str(err))
            error("Server not found!")
            try:
                mda_id = get_analysis_id()
                if mda_id:
                    log(
                        f"  (use 'wop update -s' to generate server for current analysis #{mda_id})"
                    )
                else:
                    warn("No local analysis found")
                    log(
                        "  (use 'wop pull -s <id>' to generate server for the analysis #id)"
                    )
            except ValueError as err:
                warn(str(err))
            sys.exit(-1)
        run_server(port)

    @staticmethod
    def convert(filename):
        if not os.path.exists(filename):
            error(f"File {filename} not found.")
        pathname, extension = os.path.splitext(filename)
        if not (extension == ".sqlite" or re.match(r"\.sqlite_\d+$", extension)):
            warn(
                f"File {filename} should have '.sqlite[_n]' extension, got '{extension}'"
            )
        basename = os.path.basename(pathname)
        convert_sqlite_to_csv(filename, basename)

    def _update_mda_base(self, dry_run=False, force=False):
        update_options = {
            "--dry-run": dry_run,
            "--force": force,
            "--server": False,
            "--egmdo": False,
            "--run-ops": False,
            "--test-units": False,
            "--gemseo": is_based_on(FRAMEWORK_GEMSEO),
            "--openmdao": is_based_on(FRAMEWORK_OPENMDAO),
        }
        mda_id = get_analysis_id()
        self.update_mda(mda_id, update_options, info_keep_run_ops=False)

    def _test_connection(self):
        if self.api_key:
            self.headers = {
                "Authorization": "Token token=" + self.api_key,
                "User-Agent": "wop/{}".format(__version__),
            }
            url = self.endpoint("/api/v1/versioning")
            debug(f"Test connect: {url}, {self.api_key}")
            try:
                resp = self.session.get(url, headers=self.headers)
                # special case: bad wop version < minimal required version
                if resp.status_code == requests.codes.forbidden:
                    error(resp.json()["message"])
                    sys.exit(-1)
                return resp.ok
            except requests.exceptions.ConnectionError:
                return False
        else:
            return False

    @staticmethod
    def check_http_error(resp):
        msg = None
        try:
            msg = resp.json().get("message")
        except requests.exceptions.JSONDecodeError:
            pass  # Does not contain json => ignore
        if msg:
            warn(msg)
        try:
            resp.raise_for_status()
        except requests.exceptions.HTTPError as http_err:
            error(f"HTTP Error : {http_err}")
            exit(-1)
