import os

from .data import NodeDatabase
from .responses import invalid_request, unauthorized, connect_success, connect_conflict, unexpected_hub
from .task import TaskRunner, task, Form, FormParameter, FormValidator, FormFilling, FormCheckBox


class ConnectRunner(TaskRunner):
    """
    Connects a node to a remote hub.
    """

    def prompt_path(self, form):
        """

        :param form:
        :type form: DbPathForm
        :return:
        """
        raise NotImplementedError()

    def prompt_credentials(self, form):
        """

        :param form:
        :type form: CredentialsForm
        :return:
        """
        raise NotImplementedError()


class DbPathForm(Form):
    def __init__(self):
        super().__init__()
        self.db_path = FormParameter(self)
        self.db_path_exists = FormValidator(self.db_path)


class CredentialsForm(Form):
    def __init__(self, prev_name, prev_hub_url):
        super().__init__()
        self.prev_name = prev_name
        self.prev_hub_url = prev_hub_url

        self.hub_url = FormParameter(self)
        self.hub_url_is_valid = FormValidator(self.hub_url)
        self.hub_url_points_to_expected_hub = FormValidator(self.hub_url)

        self.node_name = FormParameter(self)
        self.node_name_is_valid = FormValidator(self.node_name)
        self.node_name_is_available = FormValidator(self.node_name)

        self.access_pw = FormParameter(self)
        self.access_pw_is_correct = FormValidator(self.access_pw)

        self.allow_different_hub = FormCheckBox(
            is_selected=self.prev_hub_url is None, is_active=self.prev_hub_url is not None
        )


@task
def connect(runner):
    init_form = DbPathForm()

    @FormFilling(input_func=runner.prompt_path, forms=init_form)
    def poll_db_path():
        if not isinstance(init_form.db_path.get(), str) or not os.path.exists(init_form.db_path.get()):
            init_form.db_path_exists.fail()

    db_path = init_form.db_path.get()

    if NodeDatabase.exists_in(db_path):
        db = NodeDatabase.open(db_path)
        prev_hub_url = db.hub_url
        prev_hub_id = db.hub_id
        prev_id = db.id
        prev_name = db.name
    else:
        prev_hub_url = prev_hub_id = prev_id = prev_name = None

    credentials = CredentialsForm(prev_name=prev_name, prev_hub_url=prev_hub_url)

    @FormFilling(input_func=runner.prompt_credentials, forms=credentials)
    def call_hub(allow_new_hub=False):
        if not isinstance(credentials.node_name.get(), str) or not credentials.node_name.get().strip():
            credentials.node_name_is_valid.fail()

        resp = runner.request(
            url="{}/connect".format(credentials.hub_url.get()),
            data=dict(
                node_id=prev_id,
                hub_id=prev_hub_id if not allow_new_hub else None,
                access_pw=credentials.access_pw.get(),
                node_name=credentials.node_name.get()
            ),
            allow_connect_error=True
        )

        invalid_url, wrong_pw, success, conflict, other_hub = resp.select_match(invalid_request, unauthorized,
                                                                                connect_success, connect_conflict,
                                                                                unexpected_hub, must_match=False)

        if success and prev_id is None:
            NodeDatabase.create(
                db_path=db_path,
                node_name=credentials.node_name.get(),
                node_id=success.node_id,
                hub_id=success.hub_id,
                hub_url=credentials.hub_url.get()
            )
        elif success:
            db = NodeDatabase.open(db_path)
            db.reconnect(name=credentials.node_name.get(), hub_url=credentials.hub_url.get(), hub_id=success.hub_id)
        elif conflict:
            credentials.node_name_is_available.fail()
        elif wrong_pw:
            credentials.access_pw_is_correct.fail()
        elif other_hub:
            if credentials.allow_different_hub:
                call_hub(allow_new_hub=True)
            else:
                credentials.hub_url_points_to_expected_hub.fail()
        else:  # invalid_url or no match
            credentials.hub_url_is_valid.fail()
