import time

import backoff
import requests

from misc.log import logger

log = logger.get_logger(__name__)


def backoff_handler(details):
    log.warning("Backing off {wait:0.1f} seconds afters {tries} tries "
                "calling function {target} with args {args} and kwargs "
                "{kwargs}".format(**details))


class Trakt:
    def __init__(self, api_key):
        self.api_key = api_key
        self.headers = {
            'Content-Type': 'application/json',
            'trakt-api-version': '2',
            'trakt-api-key': self.api_key
        }

    def validate_api_key(self):
        try:
            # request trending shows to determine if api_key is valid
            payload = {'extended': 'full', 'limit': 1000}

            # make request
            req = requests.get('https://api.trakt.tv/shows/anticipated', params=payload, headers=self.headers,
                               timeout=30)
            log.debug("Request Response: %d", req.status_code)

            if req.status_code == 200:
                return True
            return False
        except Exception:
            log.exception("Exception validating api_key: ")
        return False

    ############################################################
    # Shows
    ############################################################

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_anticipated_shows(self, limit=1000, languages=None):
        try:
            processed_shows = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/shows/anticipated', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    for show in resp_json:
                        if show not in processed_shows:
                            processed_shows.append(show)

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve anticipated shows, request response: %d", req.status_code)
                    break

            if len(processed_shows):
                log.debug("Found %d anticipated shows", len(processed_shows))
                return processed_shows
            return None
        except Exception:
            log.exception("Exception retrieving anticipated shows: ")
        return None

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_trending_shows(self, limit=1000, languages=None):
        try:
            processed_shows = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/shows/trending', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    for show in resp_json:
                        if show not in processed_shows:
                            processed_shows.append(show)

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve trending shows, request response: %d", req.status_code)
                    break

            if len(processed_shows):
                log.debug("Found %d trending shows", len(processed_shows))
                return processed_shows
            return None
        except Exception:
            log.exception("Exception retrieving trending shows: ")
        return None

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_popular_shows(self, limit=1000, languages=None):
        try:
            processed_shows = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/shows/popular', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    # process list so it conforms to standard we expect ( e.g. {"show": {.....}} )
                    for show in resp_json:
                        if show not in processed_shows:
                            processed_shows.append({'show': show})

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve popular shows, request response: %d", req.status_code)
                    break

            if len(processed_shows):
                log.debug("Found %d popular shows", len(processed_shows))
                return processed_shows
            return None
        except Exception:
            log.exception("Exception retrieving popular shows: ")
        return None

    ############################################################
    # Movies
    ############################################################

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_anticipated_movies(self, limit=1000, languages=None):
        try:
            processed_movies = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/movies/anticipated', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    for movie in resp_json:
                        if movie not in processed_movies:
                            processed_movies.append(movie)

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve anticipated movies, request response: %d", req.status_code)
                    break

            if len(processed_movies):
                log.debug("Found %d anticipated movies", len(processed_movies))
                return processed_movies
            return None
        except Exception:
            log.exception("Exception retrieving anticipated movies: ")
        return None

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_trending_movies(self, limit=1000, languages=None):
        try:
            processed_movies = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/movies/trending', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    for movie in resp_json:
                        if movie not in processed_movies:
                            processed_movies.append(movie)

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve trending movies, request response: %d", req.status_code)
                    break

            if len(processed_movies):
                log.debug("Found %d trending movies", len(processed_movies))
                return processed_movies
            return None
        except Exception:
            log.exception("Exception retrieving trending movies: ")
        return None

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_popular_movies(self, limit=1000, languages=None):
        try:
            processed_movies = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/movies/popular', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    # process list so it conforms to standard we expect ( e.g. {"movie": {.....}} )
                    for movie in resp_json:
                        if movie not in processed_movies:
                            processed_movies.append({'movie': movie})

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve popular movies, request response: %d", req.status_code)
                    break

            if len(processed_movies):
                log.debug("Found %d popular movies", len(processed_movies))
                return processed_movies
            return None
        except Exception:
            log.exception("Exception retrieving popular movies: ")
        return None

    @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
    def get_boxoffice_movies(self, limit=1000, languages=None):
        try:
            processed_movies = []

            if languages is None:
                languages = ['en']

            # generate payload
            payload = {'extended': 'full', 'limit': limit, 'page': 1}
            if languages:
                payload['languages'] = ','.join(languages)

            # make request
            while True:
                req = requests.get('https://api.trakt.tv/movies/boxoffice', params=payload, headers=self.headers,
                                   timeout=30)
                log.debug("Request URL: %s", req.url)
                log.debug("Request Payload: %s", payload)
                log.debug("Response Code: %d", req.status_code)
                log.debug("Response Page: %d of %d", payload['page'],
                          0 if 'X-Pagination-Page-Count' not in req.headers else int(
                              req.headers['X-Pagination-Page-Count']))

                if req.status_code == 200:
                    resp_json = req.json()

                    for movie in resp_json:
                        if movie not in processed_movies:
                            processed_movies.append(movie)

                    # check if we have fetched the last page, break if so
                    if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There was no more pages to retrieve")
                        break
                    elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
                        log.debug("There are no more pages to retrieve results from")
                        break
                    else:
                        log.info("There are %d pages left to retrieve results from",
                                 int(req.headers['X-Pagination-Page-Count']) - payload['page'])
                        payload['page'] += 1
                        time.sleep(5)

                else:
                    log.error("Failed to retrieve boxoffice movies, request response: %d", req.status_code)
                    break

            if len(processed_movies):
                log.debug("Found %d boxoffice movies", len(processed_movies))
                return processed_movies
            return None
        except Exception:
            log.exception("Exception retrieving boxoffice movies: ")
        return None
