# -*- coding: utf-8 -*-
from __future__ import absolute_import
import logging
import io

from requests import Session
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

from guessit import guessit
from subliminal_patch.providers import Provider
from subliminal_patch.subtitle import Subtitle, guess_matches
from subliminal_patch.exceptions import TooManyRequests
from subliminal.video import Episode, Movie
from subzero.language import Language
from subliminal.exceptions import ServiceUnavailable

import gzip
import random
from time import sleep
from xml.etree import ElementTree

logger = logging.getLogger(__name__)


class BSPlayerSubtitle(Subtitle):
    """BSPlayer Subtitle."""
    provider_name = "bsplayer"
    hash_verifiable = True

    def __init__(self, language, filename, subtype, video, link, subid):
        super(BSPlayerSubtitle, self).__init__(language)
        self.language = language
        self.filename = filename
        self.page_link = link
        self.subtype = subtype
        self.video = video
        self.subid = subid

    @property
    def id(self):
        return self.subid

    @property
    def release_info(self):
        return self.filename

    def get_matches(self, video):
        matches = set()
        matches |= guess_matches(video, guessit(self.filename))

        # episode
        if isinstance(video, Episode):
            # already matched in search query
            matches.update(["title", "series", "season", "episode", "year"])

        # movie
        elif isinstance(video, Movie):
            # already matched in search query
            matches.update(["title", "year"])

        matches.add("hash")

        return matches


class BSPlayerProvider(Provider):
    """BSPlayer Provider."""

    # fmt: off
    languages = {Language('por', 'BR')} | {Language(l) for l in [
        'ara', 'bul', 'ces', 'dan', 'deu', 'ell', 'eng', 'fin', 'fra', 'hun', 'ita', 'jpn', 'kor', 'nld', 'pol', 'por',
        'ron', 'rus', 'spa', 'swe', 'tur', 'ukr', 'zho'
    ]}
    SEARCH_THROTTLE = 8
    hash_verifiable = True
    # fmt: on

    # batantly based on kodi's bsplayer plugin
    # also took from BSPlayer-Subtitles-Downloader
    def __init__(self):
        self.initialize()

    def initialize(self):
        self.session = Session()
        # Try to avoid bsplayer throttling increasing retries time (0, 4, 6, 8, 10)
        retry = Retry(connect=5, backoff_factor=2)
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount("http://", adapter)

        self.search_url = self.get_sub_domain()
        self.login()

    def terminate(self):
        self.session.close()
        self.logout()

    def api_request(self, func_name="logIn", params="", tries=5):
        headers = {
            "User-Agent": "BSPlayer/2.x (1022.12360)",
            "Content-Type": "text/xml; charset=utf-8",
            "Connection": "close",
            "SOAPAction": '"http://api.bsplayer-subtitles.com/v1.php#{func_name}"'.format(
                func_name=func_name
            ),
        }
        data = (
            '<?xml version="1.0" encoding="UTF-8"?>\n'
            '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" '
            'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" '
            'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
            'xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="{search_url}">'
            '<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
            "<ns1:{func_name}>{params}</ns1:{func_name}></SOAP-ENV:Body></SOAP-ENV:Envelope>"
        ).format(search_url=self.search_url, func_name=func_name, params=params)
        logger.debug("Sending request: %s." % func_name)
        for i in iter(range(tries)):
            try:
                self.session.headers.update(headers.items())
                res = self.session.post(self.search_url, data)
                return ElementTree.fromstring(res.text.strip())

            except Exception as ex:
                logger.error(f"Exception parsing response: {ex}")
                if func_name == "logIn":
                    self.search_url = self.get_sub_domain()

                sleep(1)

        raise TooManyRequests(f"Too many retries: {tries}")

    def login(self):
        # Setting attribute here as initialize() will reset it
        if hasattr(self, "token"):
            logger.debug("Token already met. Skipping logging")
            return True

        root = self.api_request(
            func_name="logIn",
            params=(
                "<username></username>"
                "<password></password>"
                "<AppID>BSPlayer v2.67</AppID>"
            ),
        )
        res = root.find(".//return")
        # avoid AttributeError
        if not res:
            return False

        if res.find("status").text == "OK":
            self.token = res.find("data").text
            logger.debug("Logged In Successfully.")
            return True
        return False

    def logout(self):
        # If already logged out / not logged in
        # if not self.token:
        #    return True
        if not hasattr(self, "token"):
            logger.debug("Already logged out")
            return True

        root = self.api_request(
            func_name="logOut",
            params="<handle>{token}</handle>".format(token=self.token),
        )
        res = root.find(".//return")
        self.token = None

        # avoid AttributeError
        if not res:
            logger.debug("Root logout returned None")
            return False

        if res.find("status").text == "OK":
            logger.debug("Logged Out Successfully.")
            return True

        return False

    def query(self, video, video_hash, language):
        if not self.login():
            logger.debug("Token not found. Can't perform query")
            return []

        if isinstance(language, (tuple, list, set)):
            # language_ids = ",".join(language)
            # language_ids = 'spa'
            language_ids = ",".join(sorted(l.opensubtitles for l in language))

        if video.imdb_id is None:
            imdbId = "*"
        else:
            imdbId = video.imdb_id
        sleep(self.SEARCH_THROTTLE)
        root = self.api_request(
            func_name="searchSubtitles",
            params=(
                "<handle>{token}</handle>"
                "<movieHash>{movie_hash}</movieHash>"
                "<movieSize>{movie_size}</movieSize>"
                "<languageId>{language_ids}</languageId>"
                "<imdbId>{imdbId}</imdbId>"
            ).format(
                token=self.token,
                movie_hash=video_hash,
                movie_size=video.size,
                language_ids=language_ids,
                imdbId=imdbId,
            ),
        )
        res = root.find(".//return/result")

        if not res:
            logger.debug("No subtitles found")
            return []

        status = res.find("status").text
        if status != "OK":
            logger.debug(f"No subtitles found (bad status: {status})")
            return []

        items = root.findall(".//return/data/item")
        subtitles = []
        if items:
            logger.debug("Subtitles Found.")
            for item in items:
                subID = item.find("subID").text
                subDownloadLink = item.find("subDownloadLink").text
                subLang = Language.fromopensubtitles(item.find("subLang").text)
                subName = item.find("subName").text
                subFormat = item.find("subFormat").text
                subtitles.append(
                    BSPlayerSubtitle(
                        subLang, subName, subFormat, video, subDownloadLink, subID
                    )
                )
        return subtitles

    def list_subtitles(self, video, languages):
        return self.query(video, video.hashes["bsplayer"], languages)

    def get_sub_domain(self):
        # API_URL_TEMPLATE = None
        # session = Session()
        # s1-9, s101-109

        # Don't test again
        # fixme: Setting attribute here as initialize() may reset it (maybe
        # there's a more elegant way?)
        if hasattr(self, "API_URL_TEMPLATE"):
            logger.debug(f"Working subdomain already met: {self.API_URL_TEMPLATE}")
            return self.API_URL_TEMPLATE
        else:
            self.API_URL_TEMPLATE = None

        # fmt: off
        SUB_DOMAINS = ['s1', 's2', 's3', 's4', 's5', 's6', 's7', 's8',
                       's101', 's102', 's103', 's104', 's105', 's106', 's107', 's108', 's109']
        # fmt: on
        random.shuffle(SUB_DOMAINS)
        # Limit to 8 tests
        for domain in SUB_DOMAINS[:8]:
            TEST_URL = "http://{}.api.bsplayer-subtitles.com".format(domain)
            try:
                logging.debug("Testing BSplayer sub-domain {}".format(TEST_URL))
                res = self.session.get(TEST_URL, timeout=3)
            except:
                continue
            else:
                res.raise_for_status()

                if res.status_code == 200:
                    logger.debug(f"Found working subdomain: {domain}")
                    self.API_URL_TEMPLATE = (
                        "http://{}.api.bsplayer-subtitles.com/v1.php".format(domain)
                    )
                    break
                else:
                    sleep(1)
                    continue

        if self.API_URL_TEMPLATE:
            return self.API_URL_TEMPLATE

        raise ServiceUnavailable("No API URL template was found")

    def download_subtitle(self, subtitle):
        # session = Session()
        _addheaders = {"User-Agent": "Mozilla/4.0 (compatible; Synapse)"}
        self.session.headers.update(_addheaders)
        res = self.session.get(subtitle.page_link)
        if res:
            if res.text == "500":
                raise ServiceUnavailable("Error 500 on server")

            with gzip.GzipFile(fileobj=io.BytesIO(res.content)) as gf:
                subtitle.content = gf.read()
                subtitle.normalize()

            return subtitle
        raise ServiceUnavailable("Problems conecting to the server")