from __future__ import print_function

from nile.api.v1 import (
    extractors as ne,
    datetime as nd,
    statface as ns,
    Record  # type: ignore
)
from qb2.api.v1 import (
    extractors as qe,
)

from maps.wikimap.stat.libs.common.lib import geobase_region as gb

from .feedback_processing_metrics import (
    calculate_task_metrics,
    calculate_feedback_processing_metrics,
    action_in_window_datetime
)
from .sprav_data import join_sprav
from .unfold_dimensions import unfold_common_dimensions

from .common import (
    MEMORY_LIMIT,
    DateWindowsConfig,
    cut_to_seconds, subsource_path_list, action_before_datetime, extract_some_user_id
)

FEEDBACK_DB_TABLE_PATH = '//home/maps/core/nmaps/analytics/feedback/db/feedback_latest'
REGIONS_TABLE_PATH = '//home/maps/core/nmaps/analytics/geo-data/major_regions_map'
SPRAV_FEEDBACK_TABLE_PATH = '//home/maps/core/nmaps/dynamic/replica/fbapi/feedback_task'
SPRAV_FEEDBACK_CHANGES_TABLE_PATH = '//home/maps/core/nmaps/dynamic/replica/fbapi/feedback_task_changes'

NMAPS_USERS_DIR = '//home/logfeller/logs/nmaps-users-dump-log/1d'
FBAPI_FEEDBACK_DIR = '//home/maps/core/nmaps/analytics/feedback/fbapi'

ACTION_IGNORE = 'ignore'
ACTION_NEED_INFO = 'need-info'
ACTION_UN_NEED_INFO = 'un-need-info'


def calculate_need_info_interval(history):
    def normalize_operation(item_state):
        if item_state in ('create', 'accept', 'reject', 'reveal', 'open', 'deploy'):
            return ACTION_UN_NEED_INFO
        elif item_state == 'need-info':
            return ACTION_NEED_INFO
        else:
            return ACTION_IGNORE

    need_info_at = None
    un_need_info_at = None

    for item in history:
        item_operation = normalize_operation(item['operation'])

        if item_operation == ACTION_NEED_INFO:
            need_info_at = item['modifiedAt']
            un_need_info_at = None
        elif item_operation == ACTION_UN_NEED_INFO:
            if need_info_at is not None and un_need_info_at is None:
                un_need_info_at = item['modifiedAt']

    if need_info_at is not None:
        need_info_at = cut_to_seconds(need_info_at)
    if un_need_info_at is not None:
        un_need_info_at = cut_to_seconds(un_need_info_at)

    return need_info_at, un_need_info_at


def load_important_fields(feedback_table):
    # Unused: attrs, description, hidden, duplicate_head_id, object_id
    return feedback_table.project(
        'id', 'type', 'source', 'position', 'commit_ids', 'resolution', 'reject_reason',
        qe.custom('created_at', cut_to_seconds, 'created_at'),
        qe.custom('resolved_at', cut_to_seconds, 'resolved_at'),
        qe.custom('deployed_at', cut_to_seconds, 'deployed_at'),
        qe.custom('need_info_interval', calculate_need_info_interval, 'history').hide(),
        qe.item('need_info_at', 0, 'need_info_interval'),
        qe.item('un_need_info_at', 1, 'need_info_interval'),
        resolved_by_puid=ne.custom(str, 'resolved_by')
    )


def join_fbapi(feedback_table, fbapi_table):
    def extract_yandexuid(original_task):
        if original_task is not None:
            return original_task.get('metadata', {}).get('yandexuid')
        else:
            return None

    fbapi_brief = fbapi_table.project(
        'id',
        qe.dictitem('original_task', 'fbapi_data'),
        source_branch=ne.const('toponym'),
        subsource_path=ne.custom(subsource_path_list, 'source_branch', 'original_task'),
        yandexuid=ne.custom(extract_yandexuid, 'original_task'),
        some_user_id=ne.custom(extract_some_user_id, 'original_task'),
        created_at=ne.custom(cut_to_seconds, 'created_at'),
        fbapi_created_date=ne.custom(lambda c: nd.round_period(c), 'created_at'),
    ).label('fbapi_data')

    return feedback_table.join(
        fbapi_brief,
        by='id',
        type='left',

        memory_limit=MEMORY_LIMIT,
    )


def join_users(feedback_table, users_table):
    nmaps_users_resolved = users_table.project(
        resolved_by_puid='puid',
        resolved_by_login='um_login',
        mod_status='moderation_status'
    )

    return feedback_table.join(
        nmaps_users_resolved,
        by='resolved_by_puid',
        type='left',

        memory_limit=MEMORY_LIMIT,
    )


def annotate_with_dates(feedback_table, windows_config):
    """Annotate records with fielddate and window_days

    We calculate metrics for all dates at once in a common workflow. But for this we need to have a separate dataset
    for each 'fielddate' and window. In other words, each record must have a 'fielddate' and window fields and be
    duplicated for the desired dates. We could just add each fielddate for each record from the database. But it will
    increase the amount of data len(fielddates) * len(windows) times. And we will not use most of it for different
    dates. However, the execution time will suffer significantly.

    Instead for each pair (record, fielddate) we check if it will be used at all beforehand.

    Our three kinds of metrics take into account the appropriate kind of feedback records for each date:
        * actions metrics: with 'created' and 'deployed' actions on that date;
        * unresolved metrics: currently unresolved feedback -- the date is after created and before resolved;
        * resolution metrics: with 'resolved' action on that date.

    First of all, we filter out all irrelevant feedback -- that was created after or deployed before the date range of
    interest. Then, for each (record, fielddate) pair, we check whether the record has an action with it on that date.
    If so, we take this pair. If not, we check whether the record was in an 'unresolved' state on that date and take it
    if it was. Otherwise, we ignore this pair.
    """
    left_margin = windows_config.left_margin
    right_margin = windows_config.right_margin

    def annotate_with_dates_mapper(records):
        for record in records:
            created_datetime = nd.Datetime.from_iso(record.get('created_at'))
            if created_datetime > right_margin:
                continue

            if action_before_datetime(record.get('deployed_at'), left_margin):
                continue

            for fielddate, window_days, window_start, window_end in windows_config.windows():
                do_yield = False
                for action in ('created_at', 'resolved_at', 'deployed_at'):
                    if action_in_window_datetime(record.get(action), window_start, window_end):
                        do_yield = True
                        break
                else:
                    if (created_datetime < window_end and
                            not action_before_datetime(record.get('resolved_at'), window_end)):
                        do_yield = True

                if do_yield:
                    yield Record(
                        record,
                        fielddate=fielddate,
                        window_start=window_start.date().isoformat(),
                        window_end=window_end.date().isoformat(),
                        window_days=window_days)

    return feedback_table.map(annotate_with_dates_mapper)


def construct_full_feedback_data(
    feedback_db_table, sprav_table, sprav_changes_table, nmaps_users_table,
    fbapi_issues_table
):
    return feedback_db_table \
        .call(load_important_fields) \
        .call(join_sprav, sprav_table, sprav_changes_table) \
        .call(join_fbapi, fbapi_issues_table) \
        .call(join_users, nmaps_users_table) \
        .call(gb.add_region_id_field('position', 'region_id'))


def make_testable_job(job, windows_config):
    feedback_db_table = job.table(FEEDBACK_DB_TABLE_PATH).label('feedback_db')
    sprav_table = job.table(SPRAV_FEEDBACK_TABLE_PATH).label('sprav_table')
    sprav_changes_table = job.table(SPRAV_FEEDBACK_CHANGES_TABLE_PATH).label('sprav_changes')

    nmaps_users_table = job.table('/'.join((NMAPS_USERS_DIR, windows_config.dump_date))).label('nmaps_users')
    fbapi_issues_table = job.table('/'.join((FBAPI_FEEDBACK_DIR, windows_config.dump_date))).label('fbapi_issues')
    regions_table = job.table(REGIONS_TABLE_PATH).label('regions_table')

    full_feedback_data = construct_full_feedback_data(
        feedback_db_table, sprav_table, sprav_changes_table,
        nmaps_users_table, fbapi_issues_table).label('full_feedback_data')

    feedback_processing_metrics = full_feedback_data \
        .call(annotate_with_dates, windows_config) \
        .call(calculate_task_metrics) \
        .call(unfold_common_dimensions, regions_table) \
        .call(calculate_feedback_processing_metrics)

    return feedback_processing_metrics


def make_job(
        job, from_date, to_date,
        report_path, statface_client, dump_date):
    windows_config = DateWindowsConfig(
        from_date, to_date, windows_days=[1, 7, 28], dump_date=dump_date)

    report_metrics = make_testable_job(
        job, windows_config)

    report = ns.StatfaceReport()\
        .path(report_path)\
        .scale('daily')\
        .client(statface_client)
    report_metrics.publish(report, allow_change_job=True)

    return job
