from datetime import datetime, timedelta
from . import tmdbapi
from . import api_utils

def get_pinyin_initials(text):
    if not text:
        return ""
    return api_utils.get_pinyin_from_service(text)

class TMDBMovieScraper(object):
    def __init__(self, url_settings, language, certification_country, search_language="", include_adult=False):
        self.url_settings = url_settings
        self.language = language
        self.certification_country = certification_country
        if(search_language == ""):
            self.search_language = language
        else:
            self.search_language = search_language
        self.include_adult = include_adult
        self._urls = None

    @property
    def urls(self):
        if not self._urls:
            self._urls = _load_base_urls(self.url_settings)
        return self._urls

    def _get_image_proxy(self):
        try:
            if self.url_settings:
                proxy = self.url_settings.getSettingString('image_proxy_prefix')
            else:
                proxy = ""
            if not proxy:
                proxy = 'https://wsrv.nl/?url='
            return proxy
        except:
            return 'https://wsrv.nl/?url='

    def search(self, title, year=None):

        def is_best(item):
            return item['title'].lower() == title and (
                not year or item.get('release_date', '').startswith(year))

        search_media_id = _parse_media_id(title)
        if search_media_id:
            if search_media_id['type'] == 'tmdb':
                result = _get_movie(search_media_id['id'], None, True)
                if 'error' in result:
                    return result
                result = [result]
            else:
                result = tmdbapi.find_movie_by_external_id(search_media_id['id'], language=self.search_language, settings=self.url_settings)
                if 'error' in result:
                    return result
                result = result.get('movie_results')
        else:
            response = tmdbapi.search_movie(query=title, year=year, language=self.search_language, settings=self.url_settings, include_adult=self.include_adult)
            if 'error' in response:
                return response
            result = response['results']
            # get second page if available and if first page doesn't contain an `is_best` result with popularity > 5
            if response['total_pages'] > 1:
                bests = [item for item in result if is_best(item) and item.get('popularity',0) > 5]
                if not bests:
                    response = tmdbapi.search_movie(query=title, year=year, language=self.language, page=2, settings=self.url_settings, include_adult=self.include_adult)
                    if not 'error' in response:
                        result += response['results']
        urls = self.urls

        if result:
            # move all `is_best` results at the beginning of the list, sort them by popularity (if found):
            bests_first = sorted([item for item in result if is_best(item)], key=lambda k: k.get('popularity',0), reverse=True)
            result = bests_first + [item for item in result if item not in bests_first]

        proxy = self._get_image_proxy()

        for item in result:
            if item.get('poster_path'):
                item['poster_path'] = proxy + urls['preview'] + item['poster_path']
            if item.get('backdrop_path'):
                item['backdrop_path'] = proxy + urls['preview'] + item['backdrop_path']
        return result

    def get_movie_requests(self, media_id):
        from . import tmdbapi
        details_lang = 'trailers,images,releases,casts,keywords'
        details_fallback = 'trailers,images'
        
        base_url = tmdbapi.get_base_url(self.url_settings)
        movie_url = base_url.format('movie/{}')

        req_movie = {
            'url': movie_url.format(media_id),
            'params': tmdbapi._set_params(details_lang, self.language),
            'headers': dict(tmdbapi.HEADERS),
            'type': 'tmdb_movie',
            'id': media_id
        }
        req_fallback = {
            'url': movie_url.format(media_id),
            'params': tmdbapi._set_params(details_fallback, None),
            'headers': dict(tmdbapi.HEADERS),
            'type': 'tmdb_movie_fallback',
            'id': media_id
        }
        return [req_movie, req_fallback]

    def get_collection_request(self, collection_id):
        from . import tmdbapi
        details_col = 'images'
        
        base_url = tmdbapi.get_base_url()
        collection_url = base_url.format('collection/{}')

        req_col = {
            'url': collection_url.format(collection_id),
            'params': tmdbapi._set_params(details_col, self.language),
            'headers': dict(tmdbapi.HEADERS),
            'type': 'tmdb_collection',
            'id': collection_id
        }
        req_col_fallback = {
            'url': collection_url.format(collection_id),
            'params': tmdbapi._set_params(details_col, None),
            'headers': dict(tmdbapi.HEADERS),
            'type': 'tmdb_collection_fallback',
            'id': collection_id
        }
        return [req_col, req_col_fallback]

    def parse_movie_response(self, responses):
        # responses is a dict of {type: result}
        # result is the json object or error dict
        
        movie = responses.get('tmdb_movie')
        movie_fallback = responses.get('tmdb_movie_fallback')
        
        if not movie or movie.get('error'):
            return movie
        
        if not self.include_adult and movie.get('adult'):
            return {'error': 'Adult content is disabled'}
            
        if not movie_fallback or movie_fallback.get('error'):
            movie_fallback = {}

        movie['images'] = movie_fallback.get('images', {})

        # Handle Collections
        collection = responses.get('tmdb_collection')
        collection_fallback = responses.get('tmdb_collection_fallback')
        
        if collection and collection_fallback and 'images' in collection_fallback:
            collection['images'] = collection_fallback['images']

        return self._assemble_details(movie, movie_fallback, collection, collection_fallback)

    def get_details(self, uniqueids):
        media_id = uniqueids.get('tmdb')
        if not media_id:
            imdb_id = uniqueids.get('imdb')
            if not imdb_id:
                return None

            find_results = tmdbapi.find_movie_by_external_id(imdb_id)
            if 'error' in find_results:
                return find_results
            if find_results.get('movie_results'):
                movie = find_results['movie_results'][0]
                media_id = movie['id']
            if not media_id:
                return None

        details = self._gather_details(media_id)
        if not details:
            return None
        if details.get('error'):
            return details
        return self._assemble_details(**details)

    def _gather_details(self, media_id):
        # Prepare batch requests
        # 1. Movie details (Language)
        # 2. Movie details (Fallback/English)
        
        # We need to fetch movie details first to know if there is a collection
        # But we can fetch movie and movie_fallback in parallel
        
        # Construct batch payload
        # Using internal knowledge of tmdbapi to construct requests
        # This is a bit of a hack but necessary for batching without rewriting everything
        
        from . import tmdbapi
        from . import api_utils
        import json
        
        # Prepare requests for movie and fallback
        # IMPORTANT: We must include 'append_to_response' parameters to get casts, images, etc.
        # See _get_movie helper function for what is needed
        
        details_lang = 'trailers,images,releases,casts,keywords'
        details_fallback = 'trailers,images'
        
        req_movie = {
            'url': tmdbapi.MOVIE_URL.format(media_id),
            'params': tmdbapi._set_params(details_lang, self.language),
            'headers': dict(tmdbapi.HEADERS)
        }
        req_fallback = {
            'url': tmdbapi.MOVIE_URL.format(media_id),
            'params': tmdbapi._set_params(details_fallback, None),
            'headers': dict(tmdbapi.HEADERS)
        }
        
        # Execute batch
        batch_results = api_utils.load_info_from_service(None, batch_payload=[req_movie, req_fallback])
        
        if isinstance(batch_results, dict) and 'error' in batch_results:
             # Fallback to sequential if service fails
             movie = _get_movie(media_id, self.language)
             movie_fallback = _get_movie(media_id)
        else:
             # Process batch results
             def process_result(res):
                 if res.get('json'):
                     return res['json']
                 return json.loads(res.get('text', '{}'))
                 
             movie = process_result(batch_results[0])
             movie_fallback = process_result(batch_results[1])

        if not movie or movie.get('error'):
            return movie

        movie['images'] = movie_fallback.get('images', {})

        # Handle Collections
        collection_id = movie.get('belongs_to_collection', {}).get('id') if movie.get('belongs_to_collection') else None
        
        collection = None
        collection_fallback = None
        
        if collection_id:
            # Batch fetch collection info
            # See _get_moviecollection helper
            details_col = 'images'
            
            req_col = {
                'url': tmdbapi.COLLECTION_URL.format(collection_id),
                'params': tmdbapi._set_params(details_col, self.language),
                'headers': dict(tmdbapi.HEADERS)
            }
            req_col_fallback = {
                'url': tmdbapi.COLLECTION_URL.format(collection_id),
                'params': tmdbapi._set_params(details_col, None),
                'headers': dict(tmdbapi.HEADERS)
            }
            
            batch_col_results = api_utils.load_info_from_service(None, batch_payload=[req_col, req_col_fallback])
            
            if isinstance(batch_col_results, dict) and 'error' in batch_col_results:
                collection = _get_moviecollection(collection_id, self.language)
                collection_fallback = _get_moviecollection(collection_id)
            else:
                collection = process_result(batch_col_results[0])
                collection_fallback = process_result(batch_col_results[1])

        if collection and collection_fallback and 'images' in collection_fallback:
            collection['images'] = collection_fallback['images']

        return {'movie': movie, 'movie_fallback': movie_fallback, 'collection': collection,
            'collection_fallback': collection_fallback}

    def _assemble_details(self, movie, movie_fallback, collection, collection_fallback):
        # Generate Pinyin Initials
        pinyin_initials = api_utils.get_pinyin_from_service(movie['title'])
        
        # Check setting
        write_initials = True
        write_initials_originaltitle = True
        if self.url_settings:
             write_initials = self.url_settings.getSettingBool('write_initials')
             write_initials_originaltitle = self.url_settings.getSettingBool('write_initials_originaltitle')

        # SortTitle: All pinyin combinations + Title
        sort_title = ""
        original_title = movie['original_title']

        if pinyin_initials:
            if write_initials:
                sort_title = "{}|{}".format(pinyin_initials, movie['title'])
            
            if write_initials_originaltitle:
                original_title = "{}|{}|{}".format(pinyin_initials, movie['title'], original_title)

        info = {
            'title': movie['title'],
            'originaltitle': original_title,
            'sorttitle': sort_title,
            'plot': movie.get('overview') or movie_fallback.get('overview'),
            'tagline': movie.get('tagline') or movie_fallback.get('tagline'),
            'studio': _get_names(movie['production_companies']),
            'genre': _get_names(movie['genres']),
            'country': _get_names(movie['production_countries']),
            'credits': _get_cast_members(movie['casts'], 'crew', 'Writing', ['Screenplay', 'Writer', 'Author'], self._get_image_proxy(), self.urls['original']),
            'director': _get_cast_members(movie['casts'], 'crew', 'Directing', ['Director'], self._get_image_proxy(), self.urls['original']),
            'premiered': movie['release_date'],
            'tag': _get_names(movie['keywords']['keywords'])
        }

        if 'countries' in movie['releases']:
            certcountry = self.certification_country.upper()
            for country in movie['releases']['countries']:
                if country['iso_3166_1'] == certcountry and country['certification']:
                    info['mpaa'] = country['certification']
                    break

        trailer = _parse_trailer(movie.get('trailers', {}), movie_fallback.get('trailers', {}))
        if trailer:
            info['trailer'] = trailer
        if collection:
            info['set'] = collection.get('name') or collection_fallback.get('name')
            info['setoverview'] = collection.get('overview') or collection_fallback.get('overview')
        if movie.get('runtime'):
            info['duration'] = movie['runtime'] * 60

        ratings = {'themoviedb': {'rating': float(movie['vote_average']), 'votes': int(movie['vote_count'])}}
        uniqueids = {'tmdb': str(movie['id']), 'imdb': movie['imdb_id']}
        cast = [{
                'name': actor['name'],
                'role': actor['character'],
                'thumbnail': self._get_image_proxy() + self.urls['original'] + actor['profile_path']
                    if actor['profile_path'] else "",
                'order': actor['order']
            }
            for actor in movie['casts'].get('cast', [])
        ]
        available_art = _parse_artwork(movie, collection, self.urls, self.language, self._get_image_proxy())

        _info = {'set_tmdbid': movie['belongs_to_collection'].get('id')
            if movie['belongs_to_collection'] else None}

        return {'info': info, 'ratings': ratings, 'uniqueids': uniqueids, 'cast': cast,
            'available_art': available_art, '_info': _info}

def _parse_media_id(title):
    if title.startswith('tt') and title[2:].isdigit():
        return {'type': 'imdb', 'id':title} # IMDB ID works alone because it is clear
    title = title.lower()
    if title.startswith('tmdb/') and title[5:].isdigit(): # TMDB ID
        return {'type': 'tmdb', 'id':title[5:]}
    elif title.startswith('imdb/tt') and title[7:].isdigit(): # IMDB ID with prefix to match
        return {'type': 'imdb', 'id':title[5:]}
    return None

def _get_movie(mid, language=None, search=False):
    details = None if search else \
        'trailers,images,releases,casts,keywords' if language is not None else \
        'trailers,images'
    return tmdbapi.get_movie(mid, language=language, append_to_response=details)

def _get_moviecollection(collection_id, language=None):
    if not collection_id:
        return None
    details = 'images'
    return tmdbapi.get_collection(collection_id, language=language, append_to_response=details)

def _parse_artwork(movie, collection, urlbases, language, proxy_prefix=''):
    if language:
        # Image languages don't have regional variants
        language = language.split('-')[0]
    posters = []
    landscape = []
    logos = []
    fanart = []

    if 'images' in movie:
        posters = _build_image_list_with_fallback(movie['images']['posters'], urlbases, language, proxy_prefix=proxy_prefix)
        landscape = _build_image_list_with_fallback(movie['images']['backdrops'], urlbases, language, proxy_prefix=proxy_prefix)
        logos = _build_image_list_with_fallback(movie['images']['logos'], urlbases, language, proxy_prefix=proxy_prefix)
        fanart = _build_fanart_list(movie['images']['backdrops'], urlbases, proxy_prefix=proxy_prefix)

    # Ensure TMDB's default poster (selected by language) is first
    if movie.get('poster_path') and posters:
        default_poster = movie['poster_path']
        for i, p in enumerate(posters):
            if p['url'].endswith(default_poster):
                if i != 0:
                    posters.insert(0, posters.pop(i))
                break

    setposters = []
    setlandscape = []
    setfanart = []
    if collection and 'images' in collection:
        setposters = _build_image_list_with_fallback(collection['images']['posters'], urlbases, language, proxy_prefix=proxy_prefix)
        setlandscape = _build_image_list_with_fallback(collection['images']['backdrops'], urlbases, language, proxy_prefix=proxy_prefix)
        setfanart = _build_fanart_list(collection['images']['backdrops'], urlbases, proxy_prefix=proxy_prefix)

    return {'poster': posters, 'landscape': landscape, 'fanart': fanart,
        'set.poster': setposters, 'set.landscape': setlandscape, 'set.fanart': setfanart, 'clearlogo': logos}

def _build_image_list_with_fallback(imagelist, urlbases, language, language_fallback='en', proxy_prefix=''):
    images = _build_image_list(imagelist, urlbases, [language], proxy_prefix=proxy_prefix)

    # Add backup images
    if language != language_fallback:
        images.extend(_build_image_list(imagelist, urlbases, [language_fallback], proxy_prefix=proxy_prefix))

    # Add any images if nothing set so far
    if not images:
        images = _build_image_list(imagelist, urlbases, proxy_prefix=proxy_prefix)

    return images

def _build_fanart_list(imagelist, urlbases, proxy_prefix=''):
    return _build_image_list(imagelist, urlbases, ['xx', None], proxy_prefix=proxy_prefix)

def _build_image_list(imagelist, urlbases, languages=[], proxy_prefix=''):
    result = []
    for img in imagelist:
        if languages and img['iso_639_1'] not in languages:
            continue
        if img['file_path'].endswith('.svg'):
            continue
        result.append({
            'url': proxy_prefix + urlbases['original'] + img['file_path'],
            'preview': proxy_prefix + urlbases['preview'] + img['file_path'],
            'lang': img['iso_639_1']
        })
    return result

def _get_date_numeric(datetime_):
    return (datetime_ - datetime(1970, 1, 1)).total_seconds()

def _load_base_urls(url_settings):
    urls = {}
    urls['original'] = url_settings.getSettingString('originalUrl')
    urls['preview'] = url_settings.getSettingString('previewUrl')
    last_updated = url_settings.getSettingString('lastUpdated')
    if not urls['original'] or not urls['preview'] or not last_updated or \
            float(last_updated) < _get_date_numeric(datetime.now() - timedelta(days=30)):
        conf = tmdbapi.get_configuration()
        if conf:
            urls['original'] = conf['images']['secure_base_url'] + 'original'
            urls['preview'] = conf['images']['secure_base_url'] + 'w780'
            url_settings.setSetting('originalUrl', urls['original'])
            url_settings.setSetting('previewUrl', urls['preview'])
            url_settings.setSetting('lastUpdated', str(_get_date_numeric(datetime.now())))
    return urls

def _parse_trailer(trailers, fallback):
    if trailers.get('youtube'):
        return 'plugin://plugin.video.youtube/play/?video_id='+trailers['youtube'][0]['source']
    if fallback.get('youtube'):
        return 'plugin://plugin.video.youtube/play/?video_id='+fallback['youtube'][0]['source']
    return None

def _get_names(items):
    return [item['name'] for item in items] if items else []

def _get_cast_members(casts, casttype, department, jobs, image_proxy='', base_url=''):
    result = []
    seen = set()
    if casttype in casts:
        for cast in casts[casttype]:
            if cast['department'] == department and cast['job'] in jobs and cast['name'] not in seen:
                seen.add(cast['name'])
                thumb = ''
                if image_proxy and base_url and cast.get('profile_path'):
                    thumb = image_proxy + base_url + cast['profile_path']
                result.append({'name': cast['name'], 'thumbnail': thumb})
    return result
