# -*- coding: utf-8 -*-
from asyncio import TimeoutError
from logging import getLogger
from logging import error
from chardet import detect
from yarl import URL
from aiohttp import BasicAuth
from fuclib import proxyUser
from fuclib import proxyPass
from json import loads
from parsel import Selector
from urllib.parse import urljoin
from w3lib.encoding import http_content_type_encoding
from w3lib.encoding import html_to_unicode
from aiohttp import TCPConnector, ClientSession

logger = getLogger(__name__)


def random_ua():
    """
    随机浏览器头
    :return:
    """
    import random
    user_agent_list = [
        'Mozilla/5.0 (Windows NT {WindowsNT};{WOW64}{language} rv:{Firefox}) Gecko/{builddata} Firefox/{Firefox}'.format(
            **{'WindowsNT': random.choice(["6.1", "6.2", "6.3", "10.0"]),
               'WOW64': random.choice(["", " WOW64;", " Win64;", " x64;"]),
               'language': random.choice(["", " {};".format("zh-CN")]), 'builddata': random.choice(
                    ["201{}0{}{}".format(random.randint(0, 6), random.randint(1, 9), random.randint(10, 28))]),
               'Firefox': random.choice(
                   ["50.0.1", "50.0.2", "50.0", "50.01", "50.010", "50.011", "50.02", "50.03", "50.04", "50.05",
                    "50.06", "50.07", "50.08", "50.09", "50.1.0", "51.0.1", "51.0", "51.01", "51.010", "51.011",
                    "51.012", "51.013", "51.014", "51.02", "51.03", "51.04", "51.05", "51.06", "51.07", "51.08",
                    "51.09", "52.0.1", "52.0.2", "52.0", "52.01", "52.02", "52.03", "52.04", "52.05", "52.06",
                    "52.07", "52.08", "52.09", "52.1.0", "52.1.1", "52.1.2", "52.2.0", "52.2.1", "52.3.0", "52.4.0",
                    "52.4.1", "53.0.2", "53.0.3", "53.0", "53.01", "53.010", "53.02", "53.03", "53.04", "53.05",
                    "53.06", "53.07", "53.08", "53.09", "54.0.1", "54.0", "54.01", "54.010", "54.011", "54.012",
                    "54.013", "54.02", "54.03", "54.04", "54.05", "54.06", "54.07", "54.08", "54.09", "55.0.1",
                    "55.0.2", "55.0.3", "55.0", "55.01", "55.010", "55.011", "55.012", "55.013", "55.02", "55.03",
                    "55.04", "55.05", "55.06", "55.07", "55.08", "55.09", "56.0.1", "56.0", "56.01", "56.010",
                    "56.011", "56.012", "56.02", "56.03", "56.04", "56.05", "56.06", "56.07", "56.08", "56.09",
                    "57.03", "57.04", "57.05", "57.06"]), }),
        'Mozilla/5.0 (Windows NT {WindowsNT};{WOW64}{language}) AppleWebKit/{Safari} (KHTML, '
        'like Gecko) Chrome/{Chrome} Safari/{Safari}'.format(
            **{'WindowsNT': random.choice(["6.1", "6.2", "6.3", "10"]),
               'WOW64': random.choice(["", " WOW64;", " Win64;", " x64;"]),
               'language': random.choice(["", " {};".format("zh-CN")]),
               'Chrome': '{0}.{1}.{2}.{3}'.format(random.randint(50, 61), random.randint(0, 9),
                                                  random.randint(1000, 9999), random.randint(10, 99)),
               'Safari': '{0}.{1}'.format(random.randint(100, 999), random.randint(0, 99)), }),
        'Mozilla/5.0 ({compatible}Windows NT {WindowsNT};{WOW64} MSIE {ie}.0; Trident/{Trident}.0;){Gecko}'.format(
            **{'compatible': random.choice(["", "compatible; "]),
               'WindowsNT': random.choice(["6.1", "6.2", "6.3", "10"]),
               'WOW64': random.choice(["", " WOW64;", " Win64;", " x64;"]),
               'ie': random.randint(10, 11), 'Trident': random.randint(5, 7),
               'Gecko': random.choice(["", " like Gecko;"])}),
        'Mozilla/5.0 (Windows NT {WindowsNT}; MSIE 9.0;) Opera {opera1}.{opera2}'.format(
            **{'WindowsNT': random.choice(["6.1", "6.2", "6.3", "10"]),
               'opera1': random.randint(10, 12), 'opera2': random.randint(10, 99)}),
    ]
    rs = random.choice(user_agent_list)  # 201706  firefox14 chrome63 ie9 opera2
    return rs

class Retry(object):
    def __init__(self, max_times=5):
        self.func = None
        self.max_times = max_times

    async def run(self, session, request):
        retry_times = 0
        while retry_times < self.max_times:
            try:
                return await self.func(session, request)
            except TimeoutError:
                error(f"第{retry_times}次请求超时，url：{request['url']}")
                retry_times += 1
            except Exception as e:
                error(f"第 {retry_times} 次请求报错({type(e).__name__}: {e})，url：{request['url']}")
                retry_times += 1

    def __call__(self, func):
        self.func = func
        return self.run


class Request(object):
    @staticmethod
    @Retry(1)
    async def quest(session, request):
        if request['method'].upper() == 'GET':
            async with session.get(url=URL(request['url'],
                                           encoded=request.get('url_encoded', False)),
                                   params=request.get('params', None),
                                   cookies=request.get('cookies', None),
                                   headers=request.get('headers', None),
                                   proxy=request.get('proxies', None),
                                   allow_redirects=request.get('allow_redirects', True),
                                   timeout=request.get('time_out', 180),
                                   proxy_auth=BasicAuth(proxyUser, proxyPass)) as res:
                text = await res.read()

        elif request['method'].upper() == 'POST':
            async with session.post(url=URL(request['url'],
                                            encoded=request.get('url_encoded', False)),
                                    data=request.get('data', None),
                                    json=request.get('json', None),
                                    cookies=request.get('cookies', None),
                                    headers=request.get('headers', None),
                                    proxy=request.get('proxies', None),
                                    allow_redirects=request.get('allow_redirects', True),
                                    timeout=request.get('time_out', 180),
                                    proxy_auth=BasicAuth(proxyUser, proxyPass)) as res:
                text = await res.read()
        else:
            raise "{%s}请求方式未定义，请自定义添加！" % request['method']

        if res:
            status_code = res.status
            charset = res.charset
            response = Response(url=request['url'],
                                content=text,
                                status_code=status_code,
                                charset=charset,
                                cookies=request.get('cookies', None),
                                method=request['method'],
                                headers=request.get('headers', None),
                                callback=request.get('callback', "parse"),
                                proxies=request.get('proxies', None),
                                meta=request.get('meta', None),
                                res=res)
            return response

    @staticmethod
    async def new_session(verify=False):
        connector = TCPConnector(ssl=verify)
        session = ClientSession(connector=connector, trust_env=True)
        return session

    @staticmethod
    async def get(session, request):
        async with session.get(request['url'], params=request['params'], cookies=request['cookies'],
                               headers=request['headers'], proxy=request['proxies'],
                               allow_redirects=request['allow_redirects'], timeout=request['time_out']) as res:
            text = await res.read()
            return res, text

    @staticmethod
    async def post(session, request):
        async with session.post(request['url'], data=request['data'], json=request['json'], cookies=request['cookies'],
                                headers=request['headers'], proxy=request['proxies'],
                                allow_redirects=request['allow_redirects']) as res:
            text = await res.read()
            return res, text

    @staticmethod
    async def exit(session):
        await session.close()

    @staticmethod
    def func(future):
        logger.info(future.result())
        return future.result()


class Response:
    def __init__(self,
                 url,
                 content=None,
                 status_code=None,
                 charset=None,
                 res=None,
                 cookies=None,
                 method=None,
                 headers=None,
                 callback="parse",
                 proxies=None,
                 meta=None):
        if meta is None:
            meta = {}
        self.url = url
        self.body = self.content = content
        self.status = self.status_code = status_code
        self.charset = charset
        self.res = res
        self.cookies = {k: v.value for k, v in self.res.cookies.items()} if not cookies else cookies
        self.method = method
        self.headers = headers
        self.callback = callback
        self.proxies = proxies
        self.meta = meta if meta else {}

    def __str__(self):
        return self.text

    @property
    def text(self):
        if not self.content:
            return ''
        if self.charset:
            try:
                text = self.content.decode(self.charset)
            except UnicodeDecodeError:
                try:
                    benc = http_content_type_encoding(dict(self.res.headers)['Content-Type'])
                    if benc:
                        charset = 'charset=%s' % benc
                        text = html_to_unicode(charset, self.body)[1]
                    else:
                        raise UnicodeDecodeError
                except UnicodeDecodeError:
                    try:
                        char = detect(self.content)
                        if char:
                            text = self.content.decode(char['encoding'])
                        else:
                            raise UnicodeDecodeError
                    except UnicodeDecodeError:
                        try:
                            text = self.content.decode('utf-8')
                        except UnicodeDecodeError:
                            try:
                                text = self.content.decode("GBK")
                            except UnicodeDecodeError:
                                text = self.content.decode('utf-8', "ignore")
        else:
            try:
                text = self.content.decode('utf-8')
            except UnicodeDecodeError:
                try:
                    char = detect(self.content)
                    if char:
                        text = self.content.decode(char['encoding'])
                    else:
                        raise UnicodeDecodeError
                except UnicodeDecodeError:
                    try:
                        text = self.content.decode('gb2312')
                    except UnicodeDecodeError:
                        text = self.content.decode('utf-8', "ignore")
        return text

    @property
    def json(self):
        return loads(self.text, strict=False)

    def xpath(self, x):
        r = Selector(self.text)
        return r.xpath(x)

    def css(self, x):
        r = Selector(self.text)
        return r.css(x)

    def urljoin(self, url):
        return urljoin(self.url, url)

