from nile.api.v1 import (
    statface as ns,
    aggregators as na,
    datetime as nd,
    Record,
    Job,
    stream as nstream,
)
from maps.wikimap.stat.arm.common.lib import (
    helpers,
    object_type,
    report_names,
    table_names,
)
from typing import (
    List
)
from datetime import datetime, timedelta


def add_fielddate(
    actions: nstream.Stream,
    dates: List[nd.Datetime]
) -> nstream.Stream:
    def action_fd_mapper(rows):
        for row in rows:
            modified_at = datetime.utcfromtimestamp(row.modified_at)
            for date in dates:
                date_end = date + timedelta(days=1)
                if date <= modified_at < date_end:
                    yield Record(row, fielddate=str(date.date()))

    return actions.map(
        action_fd_mapper
    ).project(
        'fielddate',
        'object_id',
        'object_type',
        'operation_type',
        'user_login',
        'user_role',
        'version',
    )


def convert_operations(actions: nstream.Stream) -> nstream.Stream:
    FIRST_LEVEL_ACTIONS = ['create', 'delete', 'disable', 'enable']
    DISTINGUISHABLE_ACTIONS = FIRST_LEVEL_ACTIONS + ['geometry', 'time']

    def simplify_operation(raw_action: str) -> str:
        if raw_action in DISTINGUISHABLE_ACTIONS:
            return raw_action
        else:
            return '_other_'

    def get_operation_hierarchy(operation: str) -> List[str]:
        hierarchy = ['_total_']
        if operation not in FIRST_LEVEL_ACTIONS:
            hierarchy.append('modify')
        hierarchy.append(operation)
        return hierarchy

    def action_mapper(rows):
        for row in rows:
            operation_list = row.operation_type.decode('utf-8')
            operation_set = set()
            for operation in operation_list.split(' '):
                operation_set.add(simplify_operation(operation))

            for operation in operation_set:
                for op_type in helpers.list_to_tree(get_operation_hierarchy(operation)):
                    yield Record(row, operation_type=op_type)

    return actions.map(action_mapper)


ROBOT_LOGIN = 'wikimaps'
ROBOT_ROLE = 'robot'
USER_ROLE = 'user'


def add_users(actions: nstream.Stream) -> nstream.Stream:
    def action_mapper(rows):
        for row in rows:
            login = row.user_login.decode('utf-8')
            role = ROBOT_ROLE if login == ROBOT_LOGIN else USER_ROLE
            for user in helpers.list_to_tree(['_total_', role, login]):
                yield Record(row, user=user)

    return actions.map(action_mapper)


def join_regions(
    actions: nstream.Stream,
    objects: nstream.Stream,
    regions: nstream.Stream
) -> nstream.Stream:
    actions_regions = actions.join(
        objects,
        by=['object_id', 'object_type']
    ).join(
        regions,
        by='region_id',
        type='left',
    ).project(
        'fielddate',
        'object_type',
        'template_id',
        'category',
        'fake',
        'operation_type',
        'user',
        'version',
        region='region_tree',
    ).map(
        helpers.fix_unknown_regions,
    )
    return actions_regions


def make_testable_job(
    job: Job,
    dates: List[nd.Datetime],
    dump_date_string: str
) -> nstream.Stream:
    actions = job.table(table_names.ACTION_TABLE_PREFIX + dump_date_string).label('actions')
    objects = job.table(table_names.OBJECT_TABLE_PREFIX + dump_date_string).label('objects')
    regions = job.table(table_names.REGIONS_TABLE_PATH).label('regions')

    actions_measured = actions.call(
        add_fielddate,
        dates
    ).label('actions_fd').call(
        convert_operations
    ).label('actions_fd_op').call(
        add_users
    ).label('actions_fb_op_user').call(
        join_regions,
        objects,
        regions
    ).label('actions_fb_op_user_region').call(
        object_type.modify_obj_type
    ).label('actions_measured')

    actions_count = actions_measured.groupby(
        'fielddate',
        'object_type',
        'region',
        'operation_type',
        'user',
    ).aggregate(
        count=na.count(),
    ).label('actions_count')

    return actions_count


def make_job(
    job: Job,
    dates: List[nd.Datetime],
    statface_client: ns.StatfaceClient,
    dump_date_string: str
) -> None:
    objects_counts = make_testable_job(job, dates, dump_date_string)

    report = ns.StatfaceReport() \
        .path(report_names.ACTIONS_COUNT) \
        .scale('daily') \
        .client(statface_client)

    objects_counts.publish(report, allow_change_job=True)
