import functools
import os
from urllib.parse import quote, urlencode

import flask
from flask import current_app as app

from orderly_set import OrderlySet

from nyaa import bencode

USED_TRACKERS = OrderlySet()


def read_trackers_from_file(file_object):
    USED_TRACKERS.clear()

    for line in file_object:
        line = line.strip()
        if line and not line.startswith('#'):
            USED_TRACKERS.add(line)
    return USED_TRACKERS


def read_trackers():
    tracker_list_file = os.path.join(app.config['BASE_DIR'], 'trackers.txt')

    if os.path.exists(tracker_list_file):
        with open(tracker_list_file, 'r') as in_file:
            return read_trackers_from_file(in_file)


def default_trackers():
    if not USED_TRACKERS:
        read_trackers()
    return USED_TRACKERS[:]


def get_trackers_and_webseeds(torrent):
    trackers = OrderlySet()
    webseeds = OrderlySet()

    # Our main one first
    main_announce_url = app.config.get('MAIN_ANNOUNCE_URL')
    if main_announce_url:
        trackers.add(main_announce_url)

    # then the user ones
    torrent_trackers = torrent.trackers  # here be webseeds too
    for torrent_tracker in torrent_trackers:
        tracker = torrent_tracker.tracker

        # separate potential webseeds
        if tracker.is_webseed:
            webseeds.add(tracker.uri)
        else:
            trackers.add(tracker.uri)

    # and finally our tracker list
    trackers.update(default_trackers())

    return list(trackers), list(webseeds)


def get_default_trackers():
    trackers = OrderlySet()

    # Our main one first
    main_announce_url = app.config.get('MAIN_ANNOUNCE_URL')
    if main_announce_url:
        trackers.add(main_announce_url)

    # and finally our tracker list
    trackers.update(default_trackers())

    return list(trackers)


@functools.lru_cache(maxsize=1024*4)
def _create_magnet(display_name, info_hash, max_trackers=5, trackers=None):
    # Unless specified, we just use default trackers
    if trackers is None:
        trackers = get_default_trackers()

    magnet_parts = [
        ('dn', display_name)
    ]
    magnet_parts.extend(
        ('tr', tracker_url)
        for tracker_url in trackers[:max_trackers]
    )

    return ''.join([
        'magnet:?xt=urn:btih:', info_hash,
        '&', urlencode(magnet_parts, quote_via=quote)
    ])


def create_magnet(torrent):
    # Since we accept both models.Torrents and ES objects,
    # we need to make sure the info_hash is a hex string
    info_hash = torrent.info_hash
    if isinstance(info_hash, (bytes, bytearray)):
        info_hash = info_hash.hex()

    return _create_magnet(torrent.display_name, info_hash)


def create_default_metadata_base(torrent, trackers=None, webseeds=None):
    if trackers is None or webseeds is None:
        db_trackers, db_webseeds = get_trackers_and_webseeds(torrent)

        trackers = db_trackers if trackers is None else trackers
        webseeds = db_webseeds if webseeds is None else webseeds

    metadata_base = {
        'created by': 'NyaaV3',
        'creation date': int(torrent.created_utc_timestamp),
        'comment': flask.url_for('torrents.view',
                                 torrent_id=torrent.id,
                                 _external=True)
        # 'encoding' : 'UTF-8' # It's almost always UTF-8 and expected, but if it isn't...
    }

    if len(trackers) > 0:
        metadata_base['announce'] = trackers[0]
    if len(trackers) > 1:
        # Yes, it's a list of lists with a single element inside.
        metadata_base['announce-list'] = [[tracker] for tracker in trackers]

    # Add webseeds
    if webseeds:
        metadata_base['url-list'] = webseeds

    return metadata_base


def create_bencoded_torrent(torrent, bencoded_info, metadata_base=None):
    ''' Creates a bencoded torrent metadata for a given torrent,
        optionally using a given metadata_base dict (note: 'info' key will be
        popped off the dict) '''
    if metadata_base is None:
        metadata_base = create_default_metadata_base(torrent)

    metadata_base['encoding'] = torrent.encoding

    # Make sure info doesn't exist on the base
    metadata_base.pop('info', None)
    prefixed_dict = {key: metadata_base[key] for key in metadata_base if key < 'info'}
    suffixed_dict = {key: metadata_base[key] for key in metadata_base if key > 'info'}

    prefix = bencode.encode(prefixed_dict)
    suffix = bencode.encode(suffixed_dict)

    bencoded_torrent = prefix[:-1] + b'4:info' + bencoded_info + suffix[1:]

    return bencoded_torrent
