import contextlib
import getpass
import json
import logging
import sys
import urllib.error

import keyring

from mini_buildd import config, events, net, util

LOG = logging.getLogger(__name__)


class Client():
    def input(self, prompt):
        return input(prompt) if self.interactive else None

    def getpass(self):
        return getpass.getpass(f"Password for {self.endpoint.geturl(with_user=True)}: ") if self.interactive else None

    def __init__(self, endpoint, auto_confirm=False, auto_save_passwords=False, interactive=sys.stdout.isatty() and sys.stdin.isatty()):
        self.endpoint = endpoint if isinstance(endpoint, net.ClientEndpoint) else net.ClientEndpoint(endpoint, protocol="http")
        self.keyring = keyring.get_keyring()
        self.interactive = interactive
        self.auto_confirm = auto_confirm
        self.auto_save_passwords = auto_save_passwords

        LOG.debug("Initialized: %s", self)

    def __str__(self):
        return f"API client: {self.endpoint} [keyring backend={self.keyring.__class__.__qualname__}, auto_confirm={self.auto_confirm}, auto_save_passwords={self.auto_save_passwords}, interactive={self.interactive}]"

    def login(self):
        user = self.endpoint.get_user()
        if user:
            password, save_password = None, False
            try:
                password = self.keyring.get_password(self.endpoint.geturl(), user)
                if not password:
                    password, save_password = self.getpass(), True
            except keyring.errors.KeyringError as e:
                LOG.warning("Keyring error: %s", e)
                password = self.getpass()

            # Login
            self.endpoint.login(password)
            LOG.debug("%s: User '%s' logged in", self.endpoint, user)

            # Logged in: Optionally save credentials
            if save_password and (self.auto_save_passwords or self.input(f"Save password for '{user}' to {self.keyring.__class__.__qualname__}: (Y)es, (N)o? ").upper() == "Y"):
                self.keyring.set_password(self.endpoint.geturl(), user, password)
            password = ""  # Maybe safer
            return True
        return False

    def api(self, command, timeout=None):
        """
        Run API call (interactive)
        """
        LOG.debug("Client.api(): %s", command)

        url, confirm_next_call, skip, result = None, False, False, None
        while result is None and not skip:
            try:
                url = command.url(endpoint=self.endpoint, with_confirm=self.auto_confirm or confirm_next_call, with_output="json")
                confirm_next_call = False
                skip = False
                self.login()
                result = json.load(self.endpoint.urlopen(url, timeout=timeout))
            except BaseException as e:
                try:
                    # Try to get rfc7807 results (embedded in non-200 HTTP responses)
                    rfc7807 = util.Rfc7807.from_json(json.loads(e.read().decode(config.CHAR_ENCODING)))  # pylint: disable=no-member
                    error = util.HTTPError(rfc7807.status, rfc7807.detail)
                except BaseException:
                    error = e

                if not self.interactive:
                    raise error from e

                bf, rs = "\033[1m", "\033[0m"  # terminal candy
                action = self.input(
                    f"\n"
                    f"== {self}\n"
                    f"== API URL: {url}\n"
                    f"{bf}** E: {str(error)}{rs}\n"
                    f"<= [{bf}u{rs}]ser change, [{bf}cC{rs}]onfirm next or all calls, [{bf}s{rs}]kip, [{bf}q{rs}]uit, []retry? "
                )
                if action == "u":
                    self.endpoint.set_user(self.input("Change user: "))
                elif action == "c":
                    confirm_next_call = True
                elif action == "C":
                    self.auto_confirm = True
                elif action in ["s"]:
                    skip = True
                elif action in ["q"]:
                    raise error from e

        return result

    def ievents(self, after=None, types=None, distribution=None, source=None, version=None, minimal_version=None, exit_on=None, fail_on=None):
        with contextlib.closing(self.endpoint.http_connect()) as conn:
            def ihttp_events():
                while True:
                    args = {"after": after.isoformat()} if after is not None else {}
                    conn.request("GET", config.URIS["events"]["attach"].join() + "?" + urllib.parse.urlencode(args))
                    response = conn.getresponse()
                    if response.status != 200:
                        raise util.HTTPError(response.status, response.reason)
                    yield events.Event.from_json(json.load(response))

            yield from events.ifilter(ihttp_events, types=types, distribution=distribution, source=source, version=version, minimal_version=minimal_version, exit_on=exit_on, fail_on=fail_on)

    def event(self, **kwargs):
        return next(self.ievents(**kwargs, exit_on=list(events.Type)))
