from nile.api.v1 import (
    filters as nf,
    aggregators as na,
    extractors as ne,
    statface as ns,
)
from qb2.api.v1 import (
    extractors as qe,
)

from common import load_feedback_db_table, prepare_feedback_table


def fix_workflow(workflow, source, type):
    """Translate workflow assigned during import to groups needed in KPI metrics

    Users of KPI dashboard suggested several patches to existing distribution of feedback onto workflow: NMAPS-9968.
    Some feedback types have to be translated to tasks workflow, tasks should be split into important/other.

    Overall there will be three kinds of feedback based on KPI SLA:
        * feedback: feedback coming from regular users;
        * tasks: feedback/tasks coming from dedicated/internal sources (like MRC, toloka);
        * tasks-minor: tasks (hypotheses) generated automatically or low priority internal feedback.

    Included translation between feedback and task workflow will be taken into account in the feedback import later.
    """
    source_to_task = {
        'sprav',
        'sprav-pedestrian-addresses-toloka',
        'sprav-change-in-base',
        'sprav-change-not-in-base',
        'sprav-pedestrian-addresses',
        'sprav-pedestrian-entrances',

        'sprav-pedestrian-onfoot',
        'lavka-pedestrian-onfoot',
        'partner-pedestrian-onfoot',
        'partner-pedestrian-indoor',

        'mrc-privateapp-bad_conditions',
        'mrc-privateapp-barrier',
        'mrc-privateapp-deadend',
        'mrc-privateapp-no_entry',

        'experiment-address-ext',
        'route-lost',
        'matcher-reason-6',
        'hypotheses-rips',
        'forbid-manoeuvre',
        'validation-addr-uniqueness',
    }

    source_to_feedback = {
        'fbapi',
        'fbapi-samsara',
        'nmaps-complaint',
        'nmaps-request-for-deletion',
        'sprav-georef',
    }

    source_type_to_task = {
        ('mrc', 'maneuver'),
        ('mrc', 'mandatory-direction-traffic-sign'),
        ('mrc', 'one-way-traffic-sign'),
        ('mrc', 'prohibited-turn-sign'),
        ('mrc', 'traffic-prohibited-sign'),

        ('gps', 'road-direction'),
    }

    if source in source_to_feedback:
        return 'feedback'
    elif source in source_to_task:
        return 'task'
    elif (source, type) in source_type_to_task:
        return 'task'
    elif workflow == 'feedback':
        return 'task'
    elif workflow == 'task':
        return 'task-minor'
    else:
        raise ValueError('Unexpected source={0} and workflow={1}'.format(source, workflow))


def filter_relevant(feedback_table):
    """Filter feedback unnecessary in common statistics

    Our goal is to calculate the lifetime of relevant feedback. For this we need to account for feedback that was
    created in the past and is unresolved for now.

    Also we don't need several kinds of feedback in common statistics. Namely they are:
        * datatesting-like sets, custom uploads (experiment-* sources);
        * aggregated in separate statistics workflow (road closures);
        * road-speed-limit from navi as it has huge deferred irrelevant backlog.
    """

    relevant = feedback_table.filter(nf.not_(nf.or_(
        nf.equals('type', 'road-closure'),
        nf.custom(lambda s: str.startswith(s, 'experiment-') and s != 'experiment-address-ext', 'source'),
        nf.and_(nf.equals('source', 'navi'), nf.equals('type', 'road-speed-limit'))
    )))

    return relevant


def run_aggregation(feedback_table):
    relevant = filter_relevant(feedback_table).label('relevant')

    age = relevant.project(
        'fielddate',
        'open_at_end',
        'need_info_at_end',
        'age_days',
        'need_info_in_window',
        'age_days_need_info',
        workflow=ne.custom(fix_workflow, 'workflow', 'source', 'type').with_type(str)
    )

    def processed_in_n_days(is_active, age, count_on_end_of_period, n):
        return is_active \
            and age is not None \
            and age <= n \
            and count_on_end_of_period == 0

    def resolved_in_n_days_need_info_aggregator(n):
        return na.count(
            predicate=nf.custom(
                lambda is_active, age, count: processed_in_n_days(is_active, age, count, n),
                'need_info_in_window',
                'age_days_need_info',
                'need_info_at_end'
            )
        )

    def get_nth_and_convert_none_to_zero(arr, index):
        if arr is None:
            return 0
        return arr[index][1] or 0

    def nth_percentile(n, percentiles_field):
        return ne.custom(
            lambda percentiles: get_nth_and_convert_none_to_zero(percentiles, n),
            percentiles_field)

    daily_agg = age.groupby('fielddate', 'workflow').aggregate(
        sum_open_now=na.sum('open_at_end'),
        max_age_days=na.max('age_days'),
        percentile_85_age_days=na.percentile('age_days', 85.0),
        percentile_99_age_days=na.percentile('age_days', 99.0),
        sum_need_info=na.sum('need_info_at_end'),
        max_age_days_need_info=na.max('age_days_need_info', missing=0),
        percentiles_age_days_need_info=na.percentile('age_days_need_info', [1.0, 5.0, 10.0, 85.0, 99.0]),
        total_count_need_info=na.count(
            predicate=nf.custom(lambda age: age > 0, 'age_days_need_info')
        ),
        resolved_in_1day_need_info=resolved_in_n_days_need_info_aggregator(1),
        resolved_in_3days_need_info=resolved_in_n_days_need_info_aggregator(3),
        resolved_in_7days_need_info=resolved_in_n_days_need_info_aggregator(7),
        resolved_in_30days_need_info=resolved_in_n_days_need_info_aggregator(30),
    ).project(
        ne.all(exclude='percentiles_age_days_need_info'),
        total_count_need_info=qe.coalesce(None, 'total_count_need_info', 0),
        percentile_low1_age_days_need_info=nth_percentile(0, 'percentiles_age_days_need_info'),
        percentile_low2_age_days_need_info=nth_percentile(1, 'percentiles_age_days_need_info'),
        percentile_low3_age_days_need_info=nth_percentile(2, 'percentiles_age_days_need_info'),
        percentile_85_age_days_need_info=nth_percentile(3, 'percentiles_age_days_need_info'),
        percentile_99_age_days_need_info=nth_percentile(4, 'percentiles_age_days_need_info'),
        resolved_in_1day_need_info=qe.coalesce(None, 'resolved_in_1day_need_info', 0),
        resolved_in_3days_need_info=qe.coalesce(None, 'resolved_in_3days_need_info', 0),
        resolved_in_7days_need_info=qe.coalesce(None, 'resolved_in_7days_need_info', 0),
        resolved_in_30days_need_info=qe.coalesce(None, 'resolved_in_30days_need_info', 0),
    ).label('daily_result')

    return daily_agg


def calculate_report(feedback_db_table, from_date_iso_str, to_date_iso_str):
    feedback_table = prepare_feedback_table(feedback_db_table, from_date_iso_str, to_date_iso_str)
    return run_aggregation(feedback_table)


def make_testable_job(job, from_date_iso_str, to_date_iso_str):
    feedback_db_table = job.table('').label('feedback_db')

    calculate_report(feedback_db_table, from_date_iso_str, to_date_iso_str)

    return job


def make_job(job, from_date_iso_str, to_date_iso_str, report_path, statface_client):
    feedback_db_table = load_feedback_db_table(job).label('feedback_db')

    daily_agg = calculate_report(feedback_db_table, from_date_iso_str, to_date_iso_str)

    report = ns.StatfaceReport()\
        .path(report_path)\
        .scale('daily')\
        .dimensions(
            ns.Date('fielddate'),
            ns.String('workflow'))\
        .measures(
            ns.Number('sum_open_now'),
            ns.Number('max_age_days'),
            ns.Number('percentile_85_age_days'),
            ns.Number('percentile_99_age_days'),
            ns.Number('sum_need_info'),
            ns.Number('max_age_days_need_info'),
            ns.Number('percentile_85_age_days_need_info'),
            ns.Number('percentile_99_age_days_need_info'),
            ns.Number('percentile_low1_age_days_need_info'),
            ns.Number('percentile_low2_age_days_need_info'),
            ns.Number('percentile_low3_age_days_need_info'),
            ns.Number('resolved_in_1day_need_info'),
            ns.Number('resolved_in_3days_need_info'),
            ns.Number('resolved_in_7days_need_info'),
            ns.Number('resolved_in_30days_need_info'),
            ns.Number('total_count_need_info'),
            )\
        .client(statface_client)
    daily_agg.publish(report, allow_change_job=True)

    return job
