"""This module provides the RatingsAndReviewsAPI class for talking to the
ratings and reviews API, plus a few helper classes.
"""

from urllib import quote_plus
from piston_mini_client import (
    PistonAPI,
    PistonResponseObject,
    PistonSerializable,
    returns,
    returns_json,
    returns_list_of,
)
from piston_mini_client.validators import validate_pattern, validate

# These are factored out as constants for if you need to work against a
# server that doesn't support both schemes (like http-only dev servers)
PUBLIC_API_SCHEME = 'http'
AUTHENTICATED_API_SCHEME = 'https'


class ReviewRequest(PistonSerializable):
    """A review request.

    Instantiate one of these objects to describe a new review you wish to
    submit, then pass it in as an argument to submit_review().
    """
    _atts = ('app_name', 'package_name', 'summary', 'version', 'review_text',
        'rating', 'language', 'origin', 'distroseries', 'arch_tag')
    app_name = ''


class ReviewsStats(PistonResponseObject):
    """A ratings summary for a package/app.

    This class will be automatically populated with JSON retrieved from the
    server.  Each ReviewStats object will have the following fields:
     * package_name
     * app_name
     * ratings_total
     * ratings_average
    """
    pass


class ReviewDetails(PistonResponseObject):
    """A detailed review description.

    This class will be automatically populated with JSON retrieved from the
    server.  Each ReviewDetails object will have the following fields:
     * id
     * package_name
     * app_name
     * language
     * date_created
     * rating
     * reviewer_username
     * summary
     * review_text
     * hide
     * version
    """
    pass


class RatingsAndReviewsAPI(PistonAPI):
    """A client for talking to the reviews and ratings API.

    If you pass no arguments into the constructor it will try to connect to
    localhost:8000 so you probably want to at least pass in the
    ``service_root`` constructor argument.
    """
    default_service_root = 'http://localhost:8000/reviews/api/1.0'
    default_content_type = 'application/x-www-form-urlencoded'

    @returns_json
    def server_status(self):
        """Check the state of the server, to see if everything's ok."""
        return self._get('server-status/', scheme=PUBLIC_API_SCHEME)

    @validate_pattern('origin', r'[0-9a-z+-.:/]+', required=False)
    @validate_pattern('distroseries', r'\w+', required=False)
    @validate('days', int, required=False)
    @returns_list_of(ReviewsStats)
    def review_stats(self, origin='any', distroseries='any', days=None,
                     valid_days=(1, 3, 7)):
        """Fetch ratings for a particular distroseries"""
        url = 'review-stats/{0}/{1}/'.format(origin, distroseries)
        if days is not None:
            # the server only knows valid_days (1,3,7) currently
            for valid_day in valid_days:
                # pick the day from valid_days that is the next bigger than
                # days
                if days <= valid_day:
                    url += 'updates-last-{0}-days/'.format(valid_day)
                    break
        return self._get(url, scheme=PUBLIC_API_SCHEME)

    @validate_pattern('language', r'\w+', required=False)
    @validate_pattern('origin', r'[0-9a-z+-.:/]+', required=False)
    @validate_pattern('distroseries', r'\w+', required=False)
    @validate_pattern('version', r'[-\w+.:~]+', required=False)
    @validate_pattern('packagename', r'[a-z0-9.+-]+')
    @validate('appname', str, required=False)
    @validate('page', int, required=False)
    @validate('sort', str, required=False)
    @returns_list_of(ReviewDetails)
    def get_reviews(self, packagename, language='any', origin='any',
                    distroseries='any', version='any', appname='',
                    page=1, sort='helpful'):
        """Fetch ratings and reviews for a particular package name.

        If any of the optional arguments are provided, fetch reviews for that
        particular app, language, origin, distroseries or version.
        """
        if appname:
            appname = quote_plus(';' + appname)
        return self._get('reviews/filter/%s/%s/%s/%s/%s%s/page/%s/%s/' % (
            language, origin, distroseries, version, packagename,
            appname, page, sort),
            scheme=PUBLIC_API_SCHEME)

    @validate('review_id', int)
    @returns(ReviewDetails)
    def get_review(self, review_id):
        """Fetch a particular review via its id."""
        return self._get('reviews/%s/' % review_id)

    @validate('review', ReviewRequest)
    @returns(ReviewDetails)
    def submit_review(self, review):
        """Submit a rating/review."""
        return self._post('reviews/', data=review,
        scheme=AUTHENTICATED_API_SCHEME, content_type='application/json')

    @validate('review_id', int)
    @validate_pattern('reason', r'[^\n]+')
    @validate_pattern('text', r'[^\n]+')
    @returns_json
    def flag_review(self, review_id, reason, text):
        """Flag a review as being inappropriate"""
        data = {'reason': reason,
                'text': text,
                }
        return self._post('reviews/%s/flags/' % review_id, data=data,
                          scheme=AUTHENTICATED_API_SCHEME)

    @validate('review_id', int)
    @validate_pattern('useful', 'True|False')
    @returns_json
    def submit_usefulness(self, review_id, useful):
        """Submit a usefulness vote."""
        return self._post('/reviews/%s/recommendations/' % review_id,
            data={'useful': useful}, scheme=AUTHENTICATED_API_SCHEME)

    @validate('review_id', int, required=False)
    @validate_pattern('username', r'[^\n]+', required=False)
    @returns_json
    def get_usefulness(self, review_id=None, username=None):
        """Get a list of usefulness filtered by username/review_id"""
        if not username and not review_id:
            return None

        data = {}

        if username:
            data['username'] = username
        if review_id:
            data['review_id'] = str(review_id)

        return self._get('usefulness/', args=data,
            scheme=PUBLIC_API_SCHEME)

    @validate('review_id', int)
    @returns_json
    def delete_review(self, review_id):
        """Delete a review"""
        return self._post('/reviews/delete/%s/' % review_id, data={},
            scheme=AUTHENTICATED_API_SCHEME)

    @validate('review_id', int)
    @validate('rating', int)
    @validate_pattern('summary', r'[^\n]+')
    @validate('review_text', str)
    @returns(ReviewDetails)
    def modify_review(self, review_id, rating, summary, review_text):
        """Modify an existing review"""
        data = {
            'rating': rating,
            'summary': summary,
            'review_text': review_text
        }
        return self._put('/reviews/modify/%s/' % review_id, data=data,
        scheme=AUTHENTICATED_API_SCHEME)
