# -*- coding: utf-8 -*-
# Copyright 2020 Green Valley Belgium NV
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# @@license_version:1.7@@

import base64
import datetime
import json
import logging
from collections import defaultdict, namedtuple
from contextlib import closing

import cloudstorage
from dateutil.relativedelta import relativedelta
from mapreduce import mapreduce_pipeline, context
from pipeline import pipeline
from pipeline.common import List

from mcfw.properties import azzert
from mcfw.rpc import returns, arguments
from rogerthat.bizz.job.send_unread_messages import CleanupGoogleCloudStorageFiles
from rogerthat.consts import MIGRATION_QUEUE, DEBUG, PIPELINE_BUCKET
from rogerthat.models import Message
from rogerthat.models.properties.messaging import MessageMemberStatusTO
from rogerthat.settings import get_server_settings
from rogerthat.utils import is_flag_set, guid, send_mail
from rogerthat.utils.app import get_app_user_tuple_by_email

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO


Stats = namedtuple('Stats', 'total received read acked')
Interval = namedtuple('Interval', 'days months')

@returns(tuple)
@arguments(year=int, week=int, week_count=int)
def get_week_range(year, week, week_count=1):
    min_date = datetime.date(year, 1, 1) + relativedelta(weeks=week)
    min_date -= relativedelta(days=min_date.weekday())
    max_date = min_date + relativedelta(weeks=week_count) - relativedelta(seconds=1)
    return min_date, max_date


@returns(tuple)
@arguments(year=int, month=int, month_count=int)
def get_month_range(year, month, month_count=1):
    min_date = datetime.date(year, month, 1)
    max_date = min_date + relativedelta(months=month_count) - relativedelta(seconds=1)
    return min_date, max_date


@returns(unicode)
@arguments(min_time=long, max_time=long, interval=Interval, skip_messages_sent_by_js_mfr=bool)
def start_job(min_time, max_time, interval=None, skip_messages_sent_by_js_mfr=False):
    key = 'user_stats_%s_%s' % (min_time, max_time)
    key += guid()
    if interval is None:
        interval = Interval(days=1, months=0)
    counter = MessageStatsPipeline(key, min_time, max_time, interval, skip_messages_sent_by_js_mfr)
    task = counter.start(idempotence_key=key, return_task=True)
    task.add(queue_name=MIGRATION_QUEUE)

    redirect_url = "%s/status?root=%s" % (counter.base_path, counter.pipeline_id)
    logging.info("UserMessageStats pipeline url: %s", redirect_url)
    return get_server_settings().baseUrl + redirect_url


@returns(unicode)
@arguments(score=long, max_score=long)
def percent(score, max_score):
    return u'%s%%' % (int(round(100.0 * score / max_score)) if max_score else 0)


@returns(int)
@arguments(timestamp=long, min_time=long, interval=Interval)
def get_interval_index(timestamp, min_time, interval):
    azzert(timestamp >= min_time)
    azzert(interval.months or interval.days)

    date = datetime.datetime.utcfromtimestamp(timestamp)
    min_date = datetime.datetime.utcfromtimestamp(min_time)
    if interval.months:
        months_diff = 12 * (date.year - min_date.year) + date.month - min_date.month
        return months_diff / interval.months
    if interval.days:
        days_diff = (date - min_date).days
        return days_diff / interval.days


@returns(unicode)
@arguments(i=int, min_date=(datetime.datetime, datetime.date), max_date=(datetime.datetime, datetime.date),
           interval=Interval)
def get_period_str(i, min_date, max_date, interval):
    interval = relativedelta(days=interval.days, months=interval.months)
    from_date = min_date + relativedelta(days=interval.days * i, months=interval.months * i)
    to_date = min(max_date, from_date + interval - relativedelta(seconds=1))
    from_date_str = from_date.strftime('%d/%m/%Y')
    to_date_str = to_date.strftime('%d/%m/%Y')
    if from_date_str == to_date_str:
        return from_date_str
    return u'%s - %s' % (from_date_str, to_date_str)


def mapper(message):
    params = context.get().mapreduce_spec.mapper.params
    if params['skip_messages_sent_by_js_mfr'] and is_flag_set(Message.FLAG_SENT_BY_JS_MFR, message.flags):
        return

    min_time, max_time, interval = params['min_time'], params['max_time'], Interval(*params['interval'])

    for i, member in enumerate(message.members):
        if member == message.sender:
            continue

        ms = message.get_member_statuses()[i]

        received = is_flag_set(MessageMemberStatusTO.STATUS_RECEIVED, ms.status) \
            and min_time <= ms.received_timestamp <= max_time
        read = received and is_flag_set(MessageMemberStatusTO.STATUS_READ, ms.status)
        acked = is_flag_set(MessageMemberStatusTO.STATUS_ACKED, ms.status) and min_time <= ms.acked_timestamp <= max_time

        stats = Stats(total=1, received=int(received), read=int(read), acked=int(acked))
        index = get_interval_index(message.creationTimestamp, min_time, interval)
        if DEBUG:
            logging.debug('MAPPER: %s, %s, %s', member.email(), index, stats)
        yield member.email(), (str(index), list(stats))


def _combine(lists):
    '''Eg. [ [4,3,2,1], [4,3,2,1], [4,3,2,1] ] --> [12,9,6,3]'''
    return map(sum, zip(*lists))


def _combine_interval_stats(new_values, prev_stats_dict):
    for v in new_values:
        index, new_stats = eval(v) if isinstance(v, basestring) else v
        prev_stats = prev_stats_dict.get(index)
        if prev_stats:
            prev_stats_dict[index] = _combine([prev_stats, new_stats])
        else:
            prev_stats_dict[index] = new_stats

    return prev_stats_dict


def combiner(key, new_values, previously_combined_values):
    '''
    key: the app_user email
    new_values: newly collected list with tuples: [(index, Stats)]
    previously_combined_values: previously combined list with tuples: [(index, Stats)]
    '''
    if DEBUG:
        logging.debug('COMBINER %s new_values: %s', key, new_values)
        logging.debug('COMBINER %s previously_combined_values: %s', key, previously_combined_values)
    prev_stats = dict(previously_combined_values)  # {index: Stats}
    combined = _combine_interval_stats(new_values, prev_stats)
    if DEBUG:
        logging.debug('COMBINER %s combined: %s', key, combined)
    for i in combined.iteritems():
        yield i


def reducer(key, values):
    '''
    key: the identifier of the team group
    values: collected list with tuples: [(index, Stats)]
    '''
    combined_stats = _combine_interval_stats(values, dict())  # Eg. {"0": [2,1], "3": [9,8]}
    app_user, app_id = get_app_user_tuple_by_email(key)
    json_line = json.dumps(dict(email=app_user.email(),
                                app_id=app_id,
                                stats=combined_stats))