from cyson import UInt
from nile.api.v1 import (
    Record,
    extractors as ne,
)
from qb2.api.v1 import (
    extractors as qe,
    filters as qf
)

from maps.wikimap.stat.tasks_payment.dictionaries.puid_map.lib.puid_tree import (
    is_partner,
    outsource_company,
    get_person,
)
from maps.wikimap.stat.tasks_payment.dictionaries.libs.users import normalize_yalogin


def _normalize_users_dump(users_dump):
    '''
    users_dump:
    | puid | um_login | moderation_status | ... |
    |------+----------+-------------------+-----|
    | ...  | ...      | ...               | ... |

    result:
    | puid | yalogin  | role              |
    +------+----------+-------------------|
    | ...  | um_login | moderation_status |
    '''
    return users_dump.project(
        puid='puid',
        yalogin=ne.custom(normalize_yalogin, 'um_login'),
        role='moderation_status',
    )


def _add_outsource_company(log, outsourcers_dump):
    '''
    log:
    | puid | yalogin | role |
    |------+---------+------|
    | ...  | ...     | ...  |

    outsourcers_dump:
    | puid | company         | ... |
    |------+-----------------+-----|
    |  ... | outsource_group | ... |

    result:
    | puid | yalogin | role | outsource_company |
    |------+---------+------+-------------------|
    | ...  | ...     | ...  | ...               |
    '''
    return log.join(
        outsourcers_dump.project(
            puid='puid',
            outsource_company=ne.custom(outsource_company, 'company'),
        ),
        by='puid',
        type='left',
        assume_small_right=True,
        assume_unique=True,
    )


def _add_partner_acl_role(log, acl_roles_dump):
    '''
    log:
    | puid | yalogin | role | outsource_company |
    |------+---------+------+-------------------|
    | ...  | ...     | ...  | ...               |

    acl_roles_dump:
    | puid | role | ... |
    |------+------+-----|
    | ...  | ...  | ... |

    result:
    | puid | yalogin | role | outsource_company | partner_acl_role    |
    |------+---------+------+-------------------+---------------------|
    | ...  | ...     | ...  | ...               | acl_roles_dump.role |
    '''
    return log.join(
        acl_roles_dump.filter(
            qf.custom(lambda acl_role: is_partner(acl_role), 'role')
        ).project(
            puid='puid',
            partner_acl_role='role',
        ),
        by='puid',
        type='left',
        assume_small_right=True,
        assume_unique=True,
    )


def _add_staff_info(log, staff_dump):
    '''
    log
    | puid | yalogin | role | outsource_company | partner_acl_role |
    |------+---------+------+-------------------+------------------|
    | ...  | ...     | ...  | ...               | ...              |

    staff_dump:
    | login | last_name | first_name | primary_department | departments_urls | nmaps_logins | ... |
    |-------+-----------+------------+--------------------+------------------+--------------+-----|
    | ...   | ...       | ...        | ...                | ...              | ...          | ... |

    result:
    | puid | yalogin | role | outsource_company | partner_acl_role | login | last_name | first_name | primary_department | departments_urls |
    |------+---------+------+-------------------+------------------+-------+-----------+------------+--------------------+------------------|
    | ...  | ...     | ...  | ...               | ...              | ...   | ...       | ...        | ...                | ...              |
    '''
    return log.join(
        staff_dump.project(
            qe.unfold('_yalogin', 'nmaps_logins').hide(),
            yalogin=ne.custom(normalize_yalogin, '_yalogin'),
            login='login',
            last_name='last_name',
            first_name='first_name',
            primary_department='primary_department',
            departments_urls='departments_urls',
        ),
        by='yalogin',
        type='left',
        assume_small_right=True,
        assume_unique=True,
    )


def _puid_mapper(records, puid_map, puid_info):
    '''
    records:
    | puid | yalogin | role | outsource_company | partner_acl_role | login | last_name | first_name | primary_department | departments_urls |
    |------+---------+------+-------------------+------------------+-------+-----------+------------+--------------------+------------------|
    | ...  | ...     | ...  | ...               | ...              | ...   | ...       | ...        | ...                | ...              |

    puid_map:
    | puid | puid_tree |
    |------+-----------|
    | ...  | ...       |

    puid_info:
    | puid | payment | involvement | group | person |
    |------+---------+-------------+-------+--------|
    | ...  | ...     | ...         | ...   | ...    |
    '''
    def decode_optional(record, column):
        value = record.get(column)
        if value is None:
            return None
        return value.decode()

    def decode_optional_list(record, column):
        values = record.get(column)
        if values is None:
            return None
        return [v.decode() for v in values]

    for record in records:
        person = get_person(
            int(record['puid']),
            record['yalogin'].decode(),
            record['role'].decode(),
            decode_optional(record, 'outsource_company'),
            decode_optional(record, 'partner_acl_role'),
            decode_optional(record, 'login'),
            decode_optional(record, 'last_name'),
            decode_optional(record, 'first_name'),
            decode_optional(record, 'primary_department'),
            decode_optional_list(record, 'departments_urls'),
        )

        puid_tree = '\t'
        for element in person['puid_tree']:
            puid_tree += element + '\t'
            puid_map(Record(puid=UInt(person['puid']), puid_tree=puid_tree.encode()))

        if len(person) > 2:
            puid_info(Record(**person).transform('puid_tree'))


def _map_puids(log):
    '''
    log:
    | puid | yalogin | role | outsource_company | partner_acl_role | login | last_name | first_name | primary_department | departments_urls |
    |------+---------+------+-------------------+------------------+-------+-----------+------------+--------------------+------------------|
    | ...  | ...     | ...  | ...               | ...              | ...   | ...       | ...        | ...                | ...              |

    puid_map:
    | puid | puid_tree |
    |------+-----------|
    | ...  | ...       |

    puid_info:
    | puid | payment | involvement | group | person |
    |------+---------+-------------+-------+--------|
    | ...  | ...     | ...         | ...   | ...    |
    '''
    return log.map(_puid_mapper)


def prepare_puid_map_and_info(
    job,
    users_dump,
    outsourcers_dump,
    acl_roles_dump,
    staff_dump
):
    log = _normalize_users_dump(users_dump)
    log = _add_outsource_company(log, outsourcers_dump)
    log = _add_partner_acl_role(log, acl_roles_dump)
    log = _add_staff_info(log, staff_dump)
    puid_map_and_info = _map_puids(log)
    return puid_map_and_info


def cast_puid(log):
    '''
    log:
    | puid | ... |
    |------+-----|
    | int  | ... |

    output:
    | puid  | ... |
    |-------+-----|
    | bytes | ... |
    '''
    return log.project(
        ne.all(exclude=['puid']),
        puid=ne.custom(lambda puid_int: str(int(puid_int)).encode(), 'puid')
    )
