from maps.wikimap.stat.libs.assessment import (
    EDITS_DOMAIN,
    MODERATION_DOMAIN,
    FEEDBACK_DOMAIN,
    TRACKER_DOMAIN,
    add_tracker_task_id,
    get_commit_task_id,
    get_feedback_task_id,
)
from maps.wikimap.stat.tasks_payment.tasks_logging.libs.parsing import isotime_normalizer

from cyson import UInt
from nile.api.v1 import extractors, filters
from nile.api.v1.stream import BatchStream
import qb2.api.v1.extractors as qb2_extractors


_COMMON_CATEGORY_ID = b'common'
_SERVICE_CATEGORY_ID = b'service'


def _filter_by_date(column_name, date):
    return filters.custom(
        lambda value: value is not None and value.startswith(date),
        column_name
    )


def _get_grades_at(date, assessment_grade_table):
    '''
    assessment_grade_table:
    | unit_id | graded_by | graded_at | ... |
    |---------+-----------+-----------+-----|
    | ...     | ...       | ...       | ... |

    result:
    | unit_id | graded_by | graded_at |
    |---------+-----------+-----------|
    | ...     | ...       | date-...  |
    '''
    return assessment_grade_table.filter(
        _filter_by_date('graded_at', date)
    ).project(
        'unit_id', 'graded_by', 'graded_at'
    )


def _add_entity(log, assessment_unit_table):
    '''
    log:
    | unit_id | graded_by | graded_at |
    |---------+-----------+-----------|
    | ...     | ...       | ...       |

    assessment_unit_table:
    | unit_id | entity_domain | entity_id (bytes) | ... |
    |---------+---------------+-------------------+-----|
    | ...     | ...           | ...               | ... |

    result:
    | graded_by | graded_at | entity_domain | entity_id (bytes) |
    |-----------+-----------+---------------+-------------------|
    | ...       | ...       | ...           | ...               |
    '''
    return log.join(
        assessment_unit_table,
        by='unit_id',
        type='left',
        assume_unique_right=True
    ).project(
        'graded_by', 'graded_at', 'entity_domain', 'entity_id'
    )


def _split(log):
    '''
    Extracts streams from log: edits_log, moderation_log, feedback_log, tracker_log.
    '''
    return BatchStream(
        log.split(
            filters.equals('entity_domain', EDITS_DOMAIN),
            filters.equals('entity_domain', MODERATION_DOMAIN),
            filters.equals('entity_domain', FEEDBACK_DOMAIN),
            filters.equals('entity_domain', TRACKER_DOMAIN),
            multisplit=True,
            strategy='stop_if_true',
        )[:-1]
    )


def _convert_entity_id_to_int(log):
    '''
    log:
    | entity_id (bytes) | ... |
    |-------------------+-----|
    | ...               | ... |

    result:
    | entity_id (int) | ... |
    |-----------------+-----|
    | ...             | ... |
    '''
    return log.project(
        extractors.all(exclude='entity_id'),
        entity_id=extractors.custom(int, 'entity_id')
    )


def _add_edits_category_id(edits_log, social_commit_event_table):
    '''
    edits_log:
    | graded_by | graded_at | entity_domain | entity_id (int) |
    |-----------+-----------+---------------+-----------------|
    | ...       | ...       | 'edits'       | ...             |

    social_commit_event_table:
    | commit_id | primary_object_category_id | ... |
    |-----------+----------------------------+-----|
    | ...       | null or <category>         | ... |

    result:
    | graded_by | graded_at | entity_domain | category_id            |
    |-----------+-----------+---------------+------------------------|
    | ...       | ...       | 'edits'       | 'common' or <category> |
    '''

    return social_commit_event_table.filter(
        filters.equals('type', b'edit'),
    ).project(
        qb2_extractors.coalesce('category_id', 'primary_object_category_id', _COMMON_CATEGORY_ID),
        entity_id='commit_id',
    ).join(
        edits_log,
        by='entity_id',
        type='right',
        assume_unique_left=True,
    ).project(
        'graded_by', 'graded_at', 'entity_domain',
        # There are no entries in `social.commit_event` table for service
        # commits. So, `category_id` could be equal Null after join.
        qb2_extractors.coalesce('category_id', 'category_id', _SERVICE_CATEGORY_ID),
    )


def _add_moderation_category_id(moderation_log, social_task_table):
    '''
    moderation_log:
    | graded_by | graded_at | entity_domain | entity_id (int) |
    |-----------+-----------+---------------+-----------------|
    | ...       | ...       | 'moderation'  | ...             |

    social_task_table:
    | event_id | primary_object_category_id | ... |
    |----------+----------------------------+-----|
    | ...      | null or <category>         | ... |

    result:
    | graded_by | graded_at | entity_domain | category_id            |
    |-----------+-----------+---------------+------------------------|
    | ...       | ...       | 'moderation'  | 'common' or <category> |
    '''
    return social_task_table.project(
        qb2_extractors.coalesce('category_id', 'primary_object_category_id', _COMMON_CATEGORY_ID),
        entity_id='event_id',
    ).join(
        moderation_log,
        by='entity_id',
        type='right',
        assume_unique_left=True,
    ).project(
        'graded_by', 'graded_at', 'entity_domain', 'category_id'
    )


def _add_task_id_to_commits_log(commits_log):
    '''
    commits_log:
    | graded_by | graded_at | entity_domain | category_id |
    |-----------+-----------+---------------+-------------|
    | ...       | ...       | ...           | ...         |

    result:
    | graded_by | graded_at | task_id                                  |
    |-----------+-----------+------------------------------------------|
    | ...       | ...       | assessment/<entity_domain>/<category_id> |
    '''
    return commits_log.project(
        extractors.all(exclude=('entity_domain', 'category_id')),
        task_id=extractors.custom(get_commit_task_id, 'entity_domain', 'category_id')
    )


def _add_feedback_source_type(feedback_log, social_feedback_task_table):
    '''
    feedback_log:
    | graded_by | graded_at | entity_domain | entity_id (int) |
    |-----------+-----------+---------------+-----------------|
    | ...       | ...       | ...           | ...             |

    social_feedback_task_table:
    | id  | source | type | ... |
    |-----+--------+------+-----|
    | ... | ...    | ...  | ... |

    result:
    | graded_by | graded_at | entity_domain | feedback_source | feedback_type |
    |-----------+-----------+---------------+-----------------+---------------|
    | ...       | ...       | ...           | ...             | ...           |
    '''
    return social_feedback_task_table.project(
        entity_id=extractors.custom(int, 'id'),
        feedback_source='source',
        feedback_type='type',
    ).join(
        feedback_log,
        by='entity_id',
        type='right',
        assume_unique_left=True,
    ).project(
        'graded_by', 'graded_at', 'entity_domain', 'feedback_source', 'feedback_type'
    )


def _add_task_id_to_feedback_log(feedback_log):
    '''
    feedback_log:
    | graded_by | graded_at | entity_domain | feedback_source | feedback_type |
    |-----------+-----------+---------------+-----------------+---------------|
    | ...       | ...       | ...           | ...             | ...           |

    result:
    | graded_by | graded_at | task_id                                                      |
    |-----------+-----------+--------------------------------------------------------------|
    | ...       | ...       | assessment/<entity_domain>/<feedback_source>/<feedback_type> |
    '''
    return feedback_log.project(
        extractors.all(exclude=('entity_domain', 'feedback_source', 'feedback_type')),
        task_id=extractors.custom(get_feedback_task_id, 'entity_domain', 'feedback_source', 'feedback_type')
    )


def _add_task_id_to_tracker_log(tracker_log, issues_table, components_table):
    '''
    tracker_log:
    | graded_by | graded_at | entity_domain | entity_id (bytes) |
    |-----------+-----------+---------------+-------------------|
    | ...       | ...       | ...           | ...               |

    issues_table:
    | key | components (array) | ... |
    |-----+--------------------+-----|
    | ... | ...                | ... |

    components_table:
    | id  | name | ... |
    |-----+------+-----|
    | ... | ...  | ... |

    result:
    | graded_by | graded_at | task_id |
    |-----------+-----------+---------|
    | ...       | ...       | ...     |
    '''
    return add_tracker_task_id(tracker_log, issues_table, components_table).project(
        'graded_by', 'graded_at', 'task_id'
    )


def _convert_to_result_format(log):
    '''
    log:
    | graded_by | graded_at | task_id |
    |-----------+-----------+---------|
    | ...       | ...       | ...     |

    Result:
    | iso_datetime | puid      | task_id                        | quantity | geom | lat_min | lon_min | lat_max | lon_max |
    |--------------+-----------+--------------------------------+----------+------+---------+---------+---------+---------|
    | graded_at    | graded_by | assessment/<entity_domain>/... |        1 | None | None    | None    | None    | None    |
    '''
    return log.project(
        iso_datetime=extractors.custom(isotime_normalizer(), 'graded_at'),
        puid=extractors.custom(lambda value: UInt(value), 'graded_by'),
        task_id='task_id',
        quantity=extractors.const(1.0),
        geom=extractors.const(None),
        lat_min=extractors.const(None),
        lon_min=extractors.const(None),
        lat_max=extractors.const(None),
        lon_max=extractors.const(None)
    )


def make_log(
    job,
    date,
    assessment_grade_table,
    assessment_unit_table,
    social_commit_event_table,
    social_feedback_task_table,
    social_task_table,
    issues_table,
    components_table
):
    date = date.encode('utf-8')

    log = _get_grades_at(date, assessment_grade_table)
    log = _add_entity(log, assessment_unit_table)
    edits_log, moderation_log, feedback_log, tracker_log = _split(log)

    edits_log = _convert_entity_id_to_int(edits_log)
    edits_log = _add_edits_category_id(edits_log, social_commit_event_table)
    edits_log = _add_task_id_to_commits_log(edits_log)

    moderation_log = _convert_entity_id_to_int(moderation_log)
    moderation_log = _add_moderation_category_id(moderation_log, social_task_table)
    moderation_log = _add_task_id_to_commits_log(moderation_log)

    feedback_log = _convert_entity_id_to_int(feedback_log)
    feedback_log = _add_feedback_source_type(feedback_log, social_feedback_task_table)
    feedback_log = _add_task_id_to_feedback_log(feedback_log)

    tracker_log = _add_task_id_to_tracker_log(tracker_log, issues_table, components_table)

    return _convert_to_result_format(
        job.concat(edits_log, moderation_log, feedback_log, tracker_log)
    )
