import json
from requests import get, put
from gauth.auth_utils import get_gmb_id, get_auth_header
from gauth.location_utils import get_all_location_ids
from django.db.models import Max
from .models import Review, Reply
from gauth.models import Location

from django.utils import timezone


STAR_REVIEW_NUM = {'STAR_RATING_UNSPECIFIED': 0, 'ONE': 1, 'TWO': 2, 'THREE': 3, 'FOUR': 4, 'FIVE': 5}
USER, GMB_ID = get_gmb_id()


def get_review_list_url(loc_id, next_page_token=''):
    # An helper function that make a url that need to consume GMB review api
    return 'https://mybusiness.googleapis.com/v4/accounts/' + GMB_ID + '/locations/' + loc_id + '/reviews?pageToken='+next_page_token


def get_reply_url(location_id, review_id):
    _, account_id = get_gmb_id()
    return f'https://mybusiness.googleapis.com/v4/accounts/{account_id}/locations/{location_id}/reviews/{review_id}/reply'


def reply_review(review, replied_text):
    '''
    reply a review with a put request.
    :param review: review object -> a review which you want to reply.
    :param replied_text: string -> The actual reply that you want to post.
    :return:
    '''
    url = get_reply_url(review.location.location_id, review.review_id)
    headers = get_auth_header()
    payload = json.dumps({'comment': replied_text})
    response = put(url, headers=headers, data=payload)
    return response


def get_max_date(loc_id):
    '''
    find the maximum date of any particular location. the latest date
    :param loc_id: Integer -> Globally unique ID for google my business locations.
    :return: DateTime Obj -> The latest reviews date of that location.
    '''
    # Function that takes a location id and return the latest updated review.
    largest = Review.objects.filter(location_id=loc_id).aggregate(Max('create_time'))['create_time__max']
    max_date = largest if largest else '1970-01-11 17:41:17.532740'
    return max_date


def filter_unrecorded_review_by_date(reviews, max_date):
    # A function that return only those reviews whose has larger value than
    # the max create_time value in review database.
    filtered_reviews = []
    for rev in reviews:
        if rev['createTime'] >= str(max_date):
            filtered_reviews.append(rev)
    return filtered_reviews


def insert_review_into_database(unrecorded_reviews, loc):
    '''
    Insert reviews to database.
    :param unrecorded_reviews: all reviews for location.
    :param loc: location that unrecorded_reviews belongs to.
    :return: It insert all reviews if it is not exits in database and return nothing.
    '''
    for review in unrecorded_reviews:
        review_id = review.get('reviewId')
        # Check the review already in database then We don't need to store again.
        rev = Review.objects.filter(pk=review_id).first()
        if rev:
            continue
        comment = review.get('comment')
        create_time = review.get('createTime')
        update_time = review.get('updateTime')
        star_rating = STAR_REVIEW_NUM[review.get('starRating')]
        reviewer = review.get('reviewer')
        reviewer_name = reviewer.get('displayName')
        reviewer_photo = reviewer.get('profilePhotoUrl')
        review_reply = review.get('reviewReply')
        # Check if it is already  replied.
        if review_reply:
            replied_text = review_reply.get('comment')
            create_time = review_reply.get('updateTime')
            reply = Reply.objects.create(replied_text=replied_text, create_time=create_time)
        else:
            reply = None

        review = Review(
            review_id=review_id,
            comment=comment,
            create_time=create_time,
            update_time=update_time,
            star_rating=star_rating,
            reviewer_name=reviewer_name,
            reviewer_photo=reviewer_photo,
            location=loc,
            reply=reply
        )
        review.save()


def sync_all_review(loc_id):
    '''
    Sync a location if any bad thing occur for example any network break.
    :param: loc_id -> Location id of a particular location
    :return: None -> It just update all reviews of this location and return nothing.
    '''
    loc = Location.objects.get(pk=loc_id)
    next_page_token = ''
    headers = get_auth_header()
    while True:
        url = get_review_list_url(loc_id, next_page_token)
        res = get(url, headers=headers)
        if res.status_code == 401:
            headers = get_auth_header()
            continue
        data = res.json()
        reviews = data['reviews']
        if len(reviews) != 0:
            insert_review_into_database(reviews, loc)
        next_page_token = data.get('nextPageToken')
        if next_page_token is None:
            break
    average_rating = data.get('averageRating')
    total_reviews = data.get('totalReviewCount')
    total_reviews_db = Review.objects.filter(location_id=loc_id).count()
    update_location_data(loc, average_rating, total_reviews, total_reviews_db)


def update_location_data(loc, average_rating, total_reviews, total_reviews_db):
    loc.average_rating = average_rating
    loc.total_review = total_reviews
    loc.total_review_DB = total_reviews_db
    loc.save()


def fetch_all_review(loc_id):
    loc = Location.objects.get(pk=loc_id)
    max_date = get_max_date(loc_id)
    headers = get_auth_header()
    url = get_review_list_url(loc_id)
    res = get(url, headers=headers)
    if res.status_code == 401:
        return
    data = res.json()
    reviews = data['reviews']
    average_rating = data.get('averageRating')
    total_reviews = data.get('totalReviewCount')
    unrecorded_reviews = filter_unrecorded_review_by_date(reviews, max_date)
    if len(unrecorded_reviews) != 0:
        insert_review_into_database(unrecorded_reviews, loc)
    total_reviews_db = Review.objects.filter(location_id=loc_id).count()
    update_location_data(loc, average_rating, total_reviews, total_reviews_db)


def populate_reviews():
    start = timezone.now()
    locations = get_all_location_ids()
    for loc_id in locations:
        fetch_all_review(loc_id)
    end = timezone.now()
    elapsed = end - start
    print('-----------------------------------------------------------------------------------------')
    print(f'Elapsed time: {elapsed.seconds//60} minutes and {elapsed.seconds % 60} secs.')
    print('-----------------------------------------------------------------------------------------')