from __future__ import unicode_literals, division, absolute_import
from builtins import *  # noqa pylint: disable=unused-import, redefined-builtin

import logging
from datetime import datetime, timedelta
import time

from sqlalchemy import Table, Column, Integer, Float, Unicode, DateTime, Date, func
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.schema import ForeignKey
from sqlalchemy.orm import relation
from dateutil.parser import parse as dateutil_parse

from flexget import db_schema, plugin
from flexget.utils.soup import get_soup
from flexget.event import event
from flexget.utils import requests
from flexget.utils.database import year_property

log = logging.getLogger('api_bluray')
Base = db_schema.versioned_base('api_bluray', 0)

# association tables
genres_table = Table('bluray_movie_genres', Base.metadata,
                     Column('movie_id', Integer, ForeignKey('bluray_movies.id')),
                     Column('genre_name', Integer, ForeignKey('bluray_genres.name')))
Base.register_table(genres_table)

BASE_URL = 'http://m.blu-ray.com/'


def bluray_request(endpoint, **params):
    full_url = BASE_URL + endpoint
    return requests.get(full_url, params=params).json(strict=False)


def extract_release_date(release_date):
    if not release_date or release_date.lower() == 'no release date':
        release_date = 'Dec 31, %s' % datetime.now().year
    return dateutil_parse(release_date).date()


class BlurayMovie(Base):
    __tablename__ = 'bluray_movies'

    id = Column(Integer, primary_key=True, autoincrement=False, nullable=False)
    name = Column(Unicode)
    url = Column(Unicode)
    release_date = Column(Date)
    year = year_property('release_date')
    runtime = Column(Integer)
    overview = Column(Unicode)
    country = Column(Unicode)
    studio = Column(Unicode)
    rating = Column(Float)
    bluray_rating = Column(Integer)
    certification = Column(Unicode)
    _genres = relation('BlurayGenre', secondary=genres_table, backref='movies')
    genres = association_proxy('_genres', 'name')
    updated = Column(DateTime, default=datetime.now, nullable=False)

    def __init__(self, title, year):
        if year:
            title_year = '{} ({})'.format(title, year)
        else:
            title_year = title

        params = {
            'search': 'bluraymovies',
            'country': 'ALL',
            'keyword': title,
            '_': str(int(time.time() * 1000))
        }

        country_params = {'_': params['_']}
        try:
            search_results = bluray_request('quicksearch/search.php', **params)['items']
            countries = bluray_request('countries.json.php', **country_params) or {}

            if not search_results:
                raise LookupError('No search results found for {} on blu-ray.com'.format(title_year))

            search_results = sorted(search_results, key=lambda k: extract_release_date(k.get('reldate')))
        except requests.RequestException as e:
            raise LookupError('Error searching for {} on blu-ray.com: {}'.format(title_year, e))

        # Simply take the first result unless year does not match
        for result in search_results:
            if year and str(year) != result['year']:
                continue

            self.id = int(result['url'].split('/')[-2])
            self.name = result['title']

            flag = result['flag']
            country_code = flag.split('/')[-1].split('.')[0].lower()  # eg. http://some/url/UK.png -> uk
            # find country based on flag url, default United States
            country = 'United States'
            for c in countries['countries']:
                if c['c'].lower() == country_code:
                    country = c['n']
            self.country = country
            self.release_date = extract_release_date(result.get('reldate'))
            self.bluray_rating = int(result['rating']) if result['rating'] else None

            # Used for parsing some more data, sadly with soup
            self.url = result['url']

            movie_info_response = requests.get(self.url).content

            movie_info = get_soup(movie_info_response)

            # runtime and rating, should be the last span tag with class subheading
            bluray_info = movie_info.find('div', attrs={'class': 'bluray'})
            bluray_info = bluray_info.find_all('span', attrs={'class': 'subheading'})[-1].text.split('|')

            self.studio = bluray_info[0].strip()

            for info in bluray_info[1:]:
                if 'min' in info:
                    self.runtime = int(info.replace('min', '').strip())
                elif 'Rated' in info:
                    self.certification = info.replace('Rated', '').strip()

            # rating
            rating_tag = movie_info.find('div', id='ratingscore')
            self.rating = float(rating_tag.text.strip()) if rating_tag else None

            # Third onecol_content contains some information we want
            onecol_content = movie_info.find_all('div', attrs={'class': 'onecol_content'})[2]

            # overview, genres etc
            contents = onecol_content.find('div').find('div')

            overview_tag = contents.find('p')
            self.overview = overview_tag.text.strip() if overview_tag else None

            # genres
            genres_table = contents.find('table')
            if not genres_table:
                break

            genres_content = genres_table.find_all('tr')
            if not genres_content:
                break

            genres = set()
            for genre in genres_content:
                genres.add(genre.find('td').text.strip())
            self._genres = [BlurayGenre(name=genre) for genre in genres]
            break
        else:
            raise LookupError('No search results found for {} on blu-ray.com'.format(title_year))


class BlurayGenre(Base):
    __tablename__ = 'bluray_genres'

    name = Column(Unicode, primary_key=True, nullable=False)


class BluraySearchResult(Base):
    __tablename__ = 'bluray_search_results'

    search = Column(Unicode, primary_key=True)
    movie_id = Column(Integer, ForeignKey('bluray_movies.id'), nullable=True)
    movie = relation(BlurayMovie)

    def __init__(self, search, movie_id=None, movie=None):
        self.search = search.lower()
        if movie_id:
            self.movie_id = movie_id
        if movie:
            self.movie = movie


class ApiBluray(object):
    """Does lookups to Blu-ray.com and provides movie information. Caches lookups."""

    @staticmethod
    def lookup(title=None, year=None, only_cached=False, session=None):
        if not title:
            raise LookupError('No criteria specified for blu-ray.com lookup')
        title_year = title + ' ({})'.format(year) if year else title

        movie_filter = session.query(BlurayMovie).filter(func.lower(BlurayMovie.name) == title.lower())
        if year:
            movie_filter = movie_filter.filter(BlurayMovie.year == year)
        movie = movie_filter.first()
        if not movie:
            found = session.query(BluraySearchResult). \
                filter(BluraySearchResult.search == title_year.lower()).first()
            if found and found.movie:
                movie = found.movie

        if movie:
            # Movie found in cache, check if cache has expired. Shamefully stolen from api_tmdb
            refresh_time = timedelta(days=2)
            if movie.release_date:
                if movie.release_date > datetime.now().date() - timedelta(days=7):
                    # Movie is less than a week old, expire after 1 day
                    refresh_time = timedelta(days=1)
                else:
                    age_in_years = (datetime.now().date() - movie.release_date).days / 365
                    refresh_time += timedelta(days=age_in_years * 5)
            if movie.updated < datetime.now() - refresh_time and not only_cached:
                log.debug('Cache has expired for %s, attempting to refresh from blu-ray.com.', movie.name)
                try:
                    updated_movie = BlurayMovie(title=title, year=year)
                except LookupError as e:
                    log.error('Error refreshing movie details for %s from blu-ray.com, cached info being used. %s',
                              title, e)
                else:
                    movie = session.merge(updated_movie)
            else:
                log.debug('Movie %s information restored from cache.', movie.name)
        else:
            if only_cached:
                raise LookupError('Movie %s not found from cache' % title_year)
            # There was no movie found in the cache, do a lookup from blu-ray.com
            log.verbose('Searching from blu-ray.com `%s`', title)

            # Add/merge movie to db
            movie = BlurayMovie(title=title, year=year)

            # Add to search results table if necessary
            if title.lower() != movie.name.lower():
                session.add(BluraySearchResult(search=title_year.lower(), movie_id=movie.id))

            session.merge(movie)

            if not movie:
                raise LookupError('Unable to find movie on blu-ray: {}'.format(title_year))

        return movie


@event('plugin.register')
def register_plugin():
    plugin.register(ApiBluray, 'api_bluray', api_ver=2, interfaces=[])
