import os
import sys
import aiohttp
import asyncio
from functools import partial
from island_backup.island_switcher import island_switcher
from jinja2 import Environment, FileSystemLoader
import click
from tqdm import tqdm
import logging
import aiosocks
from aiosocks.connector import SocksConnector
from . import network
from .network import get_data
from . import version as __version__
logging.basicConfig(level=logging.INFO, format='{asctime}:{name}:{levelname}: {message}', style='{')


##########
# constant
##########


_island_info = {
    'nimingban': {
        'CDNHOST': 'http://img1.nimingban.com',
        'headers': {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Dnt': '1',
            'Host': 'img1.nimingban.com',
            'Pragma': 'no-cache',
            'Referer': 'http://h.nimingban.com/t/117617?page=10',
            'Upgrade-Insecure-Requests': '1',
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36'
            }
        },
    'kukuku': {
        'CDNHOST': 'http://static.koukuko.com/h',
        'headers': {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Dnt': '1',
            'Host': 'static.koukuko.com',
            'Pragma': 'no-cache',
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36'
            }
    },
    'adnmb': {
        'CDNHOST': 'http://h-adnmb-com.n1.yun.tf:8999/Public/Upload',
        'headers': {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Dnt': '1',
            'Host': 'h-adnmb-com.n1.yun.tf:8999',
            'Pragma': 'no-cache',
            'Referer': 'http://h.adnmb.com/',
            'Upgrade-Insecure-Requests': '1',
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
        }
    }
}


##########
# setup
##########


bundle_env = getattr(sys, 'frozen', False)

if bundle_env:
    BASE = sys._MEIPASS
else:
    BASE = os.path.dirname(__file__)


env = Environment(loader=FileSystemLoader(os.path.join(BASE, 'templates')), trim_blocks=True)



################
# main
################


def template_render(name, **context):
    return env.get_template(name).render(**context)




class ImageManager:
    def __init__(self, image_dir, loop, max_tasks=150, force_update=False):
        self.url_set = set()
        self.sem = asyncio.Semaphore(max_tasks)
        self.busying = set()
        self.loop = loop
        self.image_dir = image_dir
        self.pdbar = tqdm(desc='image downloading...', position=1)
        # force update image
        self.force_update = force_update

    def get_image_path(self, url):
        file_name = url.split('/')[-1]
        file_path = os.path.join(self.image_dir, file_name)
        return file_path

    async def submit(self, url, headers=None):
        if url in self.url_set:
            return
        else:
            self.url_set.add(url)
        logging.debug('prepare download %s', url)
        file_path = self.get_image_path(url)

        if not self.force_update and os.path.exists(file_path):
            return

        self.busying.add(url)
        await self.sem.acquire()
        task = asyncio.ensure_future(get_data(url, as_type='read',
                                              headers=headers,
                                              callback=partial(self.save_file, file_path=file_path)))

        task.add_done_callback(lambda t: self.sem.release())
        task.add_done_callback(lambda t: self.busying.remove(url))
        task.add_done_callback(lambda t: self.status_info())

    async def save_file(self, data, url, file_path):
        content = data
        if not content:
            logging.debug('no data available')
            return

        logging.debug('save file to %s', file_path)

        with open(file_path, 'wb') as f:
            f.write(content)

    async def wait_all_task_done(self):
        logging.debug('begin waiting')
        while True:
            await asyncio.sleep(3)
            if not self.busying:
                break
        self.loop.stop()

    def status_info(self):
        self.pdbar.update()

        logging.debug('this is {} in busying'.format(len(self.busying)))
        urls = []
        for i, url in enumerate(self.busying):
            if i >= 3:
                break
            urls.append(url)

        logging.debug('urls[3] is %s', urls)





def sanitize_url(url):
    from urllib import parse
    parts = parse.urlsplit(url)
    path = '/api' + parts.path
    return parse.urlunsplit((parts.scheme, parts.netloc, path, '', ''))


def write_to_html(path, file_name, all_blocks, page_obj=None):
    thread_id = file_name
    file_name = file_name + '.html'
    save_to = os.path.join(path, file_name)
    with open(save_to, 'w', encoding='utf8') as f:
        f.write(template_render('base.html', title=thread_id, all_blocks=all_blocks, page_obj=page_obj))


def split_page_write(path, filename, blocks, page_num=50):
    if not page_num:
        write_to_html(path, filename, blocks)

    chunks = [blocks[i: i+page_num] for i in range(0, len(blocks), page_num)]
    max_page = len(chunks) - 1

    for idx, chunk in enumerate(chunks):
        page_filename = filename + '_' + str(idx)
        page_obj = {'prev': filename + '_' + str(idx-1) + '.html',
                    'next': filename + '_' + str(idx+1) + '.html'}
        if idx == 0:
            page_obj.pop('prev')
        if idx == max_page:
            page_obj.pop('next')
        write_to_html(path, page_filename, chunk, page_obj)


async def run(first_url, loop, base_dir=None, folder_name=None, image_manager=None):
    Page = island_switcher.island_page_model
    all_blocks = []
    p = await Page.from_url(first_url, page_num=1)
    process_bar = tqdm(total=p.total_page, position=0, desc='page scanning')
    process_bar.update()
    while True:
        thread_list = p.thread_list()
        for block in thread_list:
            if block.image_url:
                asyncio.ensure_future(image_manager.submit(block.image_url, headers=block.headers))
                # TODO: change this name to local_image
                block.image = 'image/' + block.image_url.split('/')[-1]
        all_blocks.extend(thread_list)

        if p.has_next():
            p = await Page.from_url(*p.next_page_info)
            process_bar.update()
        else:
            break

    split_page_write(path=base_dir, filename=folder_name, blocks=all_blocks, page_num=50)
    await image_manager.wait_all_task_done()
    process_bar.close()


def start(url, force_update):

    first_url = url
    # first_url = 'http://h.nimingban.com/t/117617'
    # first_url = 'http://h.nimingban.com/t/6048436?r=6048436'
    # first_url = 'http://h.nimingban.com/t/7317491?r=7317491'
    # first_url = sanitize_url(first_url)
    island_switcher.detect_by_url(first_url)
    first_url = island_switcher.sanitize_url(first_url)
    folder_name = island_switcher.get_folder_name(url)
    base_dir = os.path.join('backup', folder_name)
    image_dir = os.path.join(base_dir, 'image')
    os.makedirs(image_dir, exist_ok=True)

    logging.info('url is %s', first_url)
    logging.info('island is %s', island_switcher.island)
    loop = asyncio.get_event_loop()
    image_manager = ImageManager(image_dir, loop, force_update=force_update)
    loop.create_task(run(first_url, loop, base_dir=base_dir, image_manager=image_manager, folder_name=folder_name))
    loop.run_forever()


async def verify_proxy():
    url = 'https://api.github.com/users/littlezz'
    async with network.session.get(url) as r:
        status = r.status
        logging.info('test proxy status, [{}]'.format(status))
        assert r.status == 200


def cli_url_verify(ctx, param, value):
    if value is None:
        return
    if not any(i in value for i in island_switcher.available_island):
        raise click.BadParameter('Unsupported url {}:'.format(value))
    return value


def parse_ipaddress(ctx, param, value):
    if value is None:
        return
    host_port = value.split(':')
    host = host_port[0]
    if len(host_port) == 2:
        port = host_port[1]
    else:
        port = None
    return host, port


@click.command()
@click.argument('url', required=False, callback=cli_url_verify)
@click.option('-url', prompt='Please Input Url', callback=cli_url_verify)
@click.option('--debug', is_flag=True, help='enable debug mode')
@click.option('--force-update', is_flag=True, help='force update image')
@click.option('--conn-count', type=click.IntRange(1, 20), help='max conn number connector use. from 1 to 20. Default is no limit')
@click.option('--proxy', '-p', required=False, callback=parse_ipaddress, help='socks proxy address, ex, 127.0.0.1:1080')
@click.version_option(version=__version__)
def cli(url, debug, force_update, conn_count, proxy):
    click.echo('version: {}'.format(__version__))

    if debug:
        logging.root.setLevel(logging.DEBUG)
        asyncio.get_event_loop().set_debug(True)

    logging.info('conn number is %s', conn_count)
    logging.info('proxy is %s', proxy)

    conn_kwargs = dict(
        use_dns_cache=True,
        limit=conn_count,
        conn_timeout=60
    )

    if not proxy:
        _conn = aiohttp.TCPConnector(**conn_kwargs)
    else:
        _conn = SocksConnector(aiosocks.Socks5Addr(proxy[0], proxy[1]), **conn_kwargs)


    network.session = aiohttp.ClientSession(connector=_conn)

    try:
        try:
            if proxy:
                logging.info('Test whether proxy config is correct')
                loop = asyncio.get_event_loop()
                loop.run_until_complete(verify_proxy())
        except (aiohttp.errors.ProxyConnectionError, ConnectionRefusedError, AssertionError) as e:
            print('Proxy config is wrong!\n {}'.format(e))

        else:
            start(url, force_update)

    finally:
        network.session.close()
        if bundle_env:
            input('Press any key to exit')


if __name__ == '__main__':
    cli()
