"""
Provides helping function for issues.
"""
import copy
from json import JSONDecodeError
from math import ceil
from typing import Optional, List, Collection, Dict

import arrow
from pyramid.request import Request
from slugify import slugify

from dbas.database import DBDiscussionSession
from dbas.database.discussion_model import User, Issue, Language, sql_timestamp_pretty_print, \
    ClickedStatement, StatementToIssue, Statement, TextVersion
from dbas.handler.language import get_language_from_header
from dbas.helper.query import generate_short_url
from dbas.helper.url import UrlManager
from dbas.lib import get_enabled_issues_as_query, nick_of_anonymous_user, get_enabled_statement_as_query
from dbas.strings.keywords import Keywords as _
from dbas.strings.translator import Translator


def prepare_json_of_issue(db_issue: Issue, db_user: User) -> Dict:
    """
    Prepares slug, info, argument count and the date of the issue as dict

    :param db_issue: Issue
    :param db_user: User
    :return: Issue-dict()
    """
    slug = db_issue.slug
    title = db_issue.title
    info = db_issue.info
    long_info = db_issue.long_info
    stat_count = len(db_issue.all_arguments)
    lang = db_issue.lang
    date_pretty = sql_timestamp_pretty_print(db_issue.date, lang)
    duration = (arrow.utcnow() - db_issue.date)
    days, seconds = duration.days, duration.seconds
    duration = ceil(days * 24 + seconds / 3600)
    date_ms = int(db_issue.date.format('X')) * 1000
    date = db_issue.date.format('DD.MM.YY')
    time = db_issue.date.format('HH:mm')

    all_array = [get_issue_dict_for(issue, db_issue.uid, lang) for issue in
                 db_user.accessible_issues if issue.uid != db_issue.uid]

    _t = Translator(lang)
    tooltip = _t.get(_.discussionInfoTooltipSg) if stat_count == 1 else _t.get(_.discussionInfoTooltipPl)
    tooltip = tooltip.format(date, time, stat_count)

    decision_process = db_issue.decision_process

    return {
        'slug': slug,
        'lang': lang,
        'info': info,
        'long_info': long_info,
        'title': title,
        'uid': db_issue.uid,
        'stat_count': stat_count,
        'date': date,
        'date_ms': date_ms,
        'date_pretty': date_pretty,
        'all': all_array,
        'tooltip': tooltip,
        'intro': _t.get(_.currentDiscussion),
        'duration': duration,
        'read_only': db_issue.is_read_only,
        'decidotron_budget': decision_process.to_dict() if decision_process else None
    }


def get_number_of_authors(issue_uid: int) -> int:
    """
    Returns number of active users for the issue

    :param issue_uid: Issue Issue.uid
    :return: Integer
    """
    issues_statements_uids = [el.statement_uid for el in
                              DBDiscussionSession.query(StatementToIssue).filter_by(issue_uid=issue_uid).all()]
    active_statements_uids = [el.uid for el in
                              get_enabled_statement_as_query().filter(Statement.uid.in_(issues_statements_uids)).all()]

    active_users = [el.author_uid for el in DBDiscussionSession.query(TextVersion).filter(
        TextVersion.statement_uid.in_(active_statements_uids))]

    return len(set(active_users))


def get_issue_dict_for(db_issue: Issue, uid: int, lang: str) -> dict:
    """
    Creates an dictionary for the issue

    :param db_issue: Issue
    :param uid: current selected Issue.uid
    :param lang: ui_locales
    :return: dict()
    """
    _um = UrlManager(db_issue.slug)
    issue_dict = {
        'uid': str(db_issue.uid),
        'slug': db_issue.slug,
        'title': db_issue.title,
        'url': '/' + db_issue.slug,
        'review_url': _um.get_review_url() if str(uid) != str(db_issue.uid) else '',
        'info': db_issue.info,
        'stat_count': len(db_issue.statements),
        'date': sql_timestamp_pretty_print(db_issue.date, lang),
        'author': db_issue.author.public_nickname,
        'error': '',
        'author_url': '/user/{}'.format(db_issue.author.uid),
        'enabled': 'disabled' if str(uid) == str(db_issue.uid) else 'enabled'
    }
    return issue_dict


def get_id_of_slug(slug: str) -> Issue:
    """
    Returns the uid of the issue with given slug

    :param slug: slug
    :return: uid
    """
    return get_enabled_issues_as_query().filter_by(slug=slug).first()


def save_issue_in_session(issue: Issue, request: Request):
    """

    :param issue:
    :param request:
    :return:
    """
    request.session['issue'] = issue.uid


def get_issue_id(request) -> Optional[int]:
    """
    Returns issue uid saved in request. If there is no uid, we will choose an
    issue based on the language from the requests header

    :param request: self.request
    :return: uid
    """
    issue_uid = None
    try:
        issue_uid = request.json_body.get('issue')
    except (JSONDecodeError, AttributeError):
        pass
    if not issue_uid:
        issue_uid = request.matchdict.get('issue')
    if not issue_uid:
        issue_uid = request.params.get('issue')
    if not issue_uid:
        issue_uid = request.session.get('issue')

    # no issue found
    if not issue_uid:
        return None

    # save issue in session
    request.session['issue'] = issue_uid
    return issue_uid


def get_issue_based_on_header(request):
    """

    :param request:
    :return:
    """
    # logger('IssueHelper', 'get_issue_based_on_header', 'no saved issue found')
    ui_locales = get_language_from_header(request)
    db_issues = get_enabled_issues_as_query()
    db_lang = DBDiscussionSession.query(Language).filter_by(ui_locales=ui_locales).first()
    db_issue = db_issues.filter_by(lang_uid=db_lang.uid).first()
    if not db_issue:
        db_issue = db_issues.first()

    return db_issue.uid


def get_title_for_slug(slug) -> Optional[str]:
    """
    Returns the issues title for a given slug

    :param slug: String
    :return: String
    """
    db_issues = DBDiscussionSession.query(Issue).all()
    for issue in db_issues:
        if str(slugify(issue.title)) == str(slug):
            return issue.title
    return None


def get_issues_overview_for(db_user: User, app_url: str) -> Dict[str, Collection]:
    """
    Returns dictionary with keywords 'user' and 'others', which got lists with dicts with infos
    IMPORTANT: URL's are generated for the frontend!

    :param db_user: User
    :param app_url: current applications url
    :return: dict
    """

    if not db_user or db_user.nickname == nick_of_anonymous_user:
        return {
            'user': [],
            'other': []
        }

    if db_user.is_admin():
        db_issues_other_users = DBDiscussionSession.query(Issue).filter(Issue.author != db_user).all()
    else:
        db_issues_other_users = [issue for issue in db_user.accessible_issues if issue.author != db_user]

    db_issues_of_user = DBDiscussionSession.query(Issue).filter_by(author=db_user).order_by(
        Issue.uid.asc()).all()

    return {
        'user': [__create_issue_dict(issue, app_url) for issue in db_issues_of_user],
        'other': [__create_issue_dict(issue, app_url) for issue in db_issues_other_users]
    }


def get_issues_overview_on_start(db_user: User) -> dict:
    """
    Returns list with title, date, and count of statements for each visible issue

    :param db_user: User
    :return:
    """
    db_issues: List[Issue] = db_user.accessible_issues
    db_issues.sort(key=lambda issue: issue.uid)
    readable = []
    writable = []
    featured = []

    for index, db_issue in enumerate(db_issues):
        issue_dict = {
            'uid': db_issue.uid,
            'url': '/discuss/' + db_issue.slug,
            'statements': len(db_issue.statements),
            'active_users': get_number_of_authors(db_issue.uid),
            'title': db_issue.title,
            'date': db_issue.date.format('DD.MM.YY HH:mm'),
            'lang': {
                'is_de': db_issue.lang == 'de',
                'is_en': db_issue.lang == 'en',
            },
            'featured': db_issue.is_featured
        }
        if db_issue.is_read_only:
            readable.append(issue_dict)
        else:
            writable.append(issue_dict)

        if db_issue.is_featured:
            featured_issue_dict = copy.deepcopy(issue_dict)
            featured_issue_dict['info'] = db_issue.info
            featured.append(featured_issue_dict)

    return {
        'issues': {
            'readable': readable,
            'writable': writable,
            'featured': featured
        }
    }


def set_discussions_properties(db_user: User, db_issue: Issue, value, iproperty, translator) -> dict:
    """

    :param db_user: User
    :param db_issue: Issue
    :param value: The value which should be assigned to property
    :param iproperty: Property of Issue, e.g. is_disabled
    :param translator:
    :return:
    """
    if db_issue.author_uid != db_user.uid and not db_user.is_admin():
        return {'error': translator.get(_.noRights)}

    if iproperty == 'enable':
        db_issue.set_disabled(not value)
    elif iproperty == 'public':
        db_issue.set_private(not value)
    elif iproperty == 'writable':
        db_issue.set_read_only(not value)
    else:
        return {'error': translator.get(_.internalKeyError)}

    return {'error': ''}


def __create_issue_dict(db_issue: Issue, app_url: str) -> dict:
    """
    Returns dictionary with several information about the given issue

    :param db_issue: database row of issue
    :param app_url: current applications url
    :return: dict()
    """
    short_url_dict = generate_short_url(app_url + '/discuss/' + db_issue.slug)
    url = short_url_dict['url'] if len(short_url_dict['url']) > 0 else app_url + '/discuss/' + db_issue.slug

    # we do nto have to check for clicked arguments, cause arguments consist out of statements
    statements = [el.statement_uid for el in
                  DBDiscussionSession.query(StatementToIssue).filter_by(issue_uid=db_issue.uid).all()]
    db_clicked_statements = DBDiscussionSession.query(ClickedStatement).filter(
        ClickedStatement.statement_uid.in_(statements)).all()

    authors_clicked_statement = [click.author_uid for click in db_clicked_statements]
    db_authors_len = DBDiscussionSession.query(User).filter(User.uid.in_(authors_clicked_statement)).count()

    prepared_dict = {
        'uid': db_issue.uid,
        'title': db_issue.title,
        'url': '/' + db_issue.slug,
        'short_url': url,
        'date': db_issue.date.format('DD.MM.YY HH:mm'),
        'count_of_statements': len(statements),
        'is_enabled': not db_issue.is_disabled,
        'is_public': not db_issue.is_private,
        'is_writable': not db_issue.is_read_only,
        'participants': db_authors_len,
        'lang': {
            'is_de': db_issue.lang == 'de',
            'is_en': db_issue.lang == 'en',
        }
    }
    return prepared_dict

"""
Provides helping function for issues.
"""
import copy
from json import JSONDecodeError
from math import ceil
from typing import Optional, List, Collection, Dict

import arrow
from pyramid.request import Request
from slugify import slugify

from dbas.database import DBDiscussionSession
from dbas.database.discussion_model import User, Issue, Language, sql_timestamp_pretty_print, \
    ClickedStatement, StatementToIssue, Statement, TextVersion
from dbas.handler.language import get_language_from_header
from dbas.helper.query import generate_short_url
from dbas.helper.url import UrlManager
from dbas.lib import get_enabled_issues_as_query, nick_of_anonymous_user, get_enabled_statement_as_query
from dbas.strings.keywords import Keywords as _
from dbas.strings.translator import Translator


def prepare_json_of_issue(db_issue: Issue, db_user: User) -> Dict:
    """
    Prepares slug, info, argument count and the date of the issue as dict

    :param db_issue: Issue
    :param db_user: User
    :return: Issue-dict()
    """
    slug = db_issue.slug
    title = db_issue.title
    info = db_issue.info
    long_info = db_issue.long_info
    stat_count = len(db_issue.all_arguments)
    lang = db_issue.lang
    date_pretty = sql_timestamp_pretty_print(db_issue.date, lang)
    duration = (arrow.utcnow() - db_issue.date)
    days, seconds = duration.days, duration.seconds
    duration = ceil(days * 24 + seconds / 3600)
    date_ms = int(db_issue.date.format('X')) * 1000
    date = db_issue.date.format('DD.MM.YY')
    time = db_issue.date.format('HH:mm')

    all_array = [get_issue_dict_for(issue, db_issue.uid, lang) for issue in
                 db_user.accessible_issues if issue.uid != db_issue.uid]

    _t = Translator(lang)
    tooltip = _t.get(_.discussionInfoTooltipSg) if stat_count == 1 else _t.get(_.discussionInfoTooltipPl)
    tooltip = tooltip.format(date, time, stat_count)

    decision_process = db_issue.decision_process

    return {
        'slug': slug,
        'lang': lang,
        'info': info,
        'long_info': long_info,
        'title': title,
        'uid': db_issue.uid,
        'stat_count': stat_count,
        'date': date,
        'date_ms': date_ms,
        'date_pretty': date_pretty,
        'all': all_array,
        'tooltip': tooltip,
        'intro': _t.get(_.currentDiscussion),
        'duration': duration,
        'read_only': db_issue.is_read_only,
        'decidotron_budget': decision_process.to_dict() if decision_process else None
    }


def get_number_of_authors(issue_uid: int) -> int:
    """
    Returns number of active users for the issue

    :param issue_uid: Issue Issue.uid
    :return: Integer
    """
    issues_statements_uids = [el.statement_uid for el in
                              DBDiscussionSession.query(StatementToIssue).filter_by(issue_uid=issue_uid).all()]
    active_statements_uids = [el.uid for el in
                              get_enabled_statement_as_query().filter(Statement.uid.in_(issues_statements_uids)).all()]

    active_users = [el.author_uid for el in DBDiscussionSession.query(TextVersion).filter(
        TextVersion.statement_uid.in_(active_statements_uids))]

    return len(set(active_users))


def get_issue_dict_for(db_issue: Issue, uid: int, lang: str) -> dict:
    """
    Creates an dictionary for the issue

    :param db_issue: Issue
    :param uid: current selected Issue.uid
    :param lang: ui_locales
    :return: dict()
    """
    _um = UrlManager(db_issue.slug)
    issue_dict = {
        'uid': str(db_issue.uid),
        'slug': db_issue.slug,
        'title': db_issue.title,
        'url': '/' + db_issue.slug,
        'review_url': _um.get_review_url() if str(uid) != str(db_issue.uid) else '',
        'info': db_issue.info,
        'stat_count': len(db_issue.statements),
        'date': sql_timestamp_pretty_print(db_issue.date, lang),
        'author': db_issue.author.public_nickname,
        'error': '',
        'author_url': '/user/{}'.format(db_issue.author.uid),
        'enabled': 'disabled' if str(uid) == str(db_issue.uid) else 'enabled'
    }
    return issue_dict


def get_id_of_slug(slug: str) -> Issue:
    """
    Returns the uid of the issue with given slug

    :param slug: slug
    :return: uid
    """
    return get_enabled_issues_as_query().filter_by(slug=slug).first()


def save_issue_in_session(issue: Issue, request: Request):
    """

    :param issue:
    :param request:
    :return:
    """
    request.session['issue'] = issue.uid


def get_issue_id(request) -> Optional[int]:
    """
    Returns issue uid saved in request. If there is no uid, we will choose an
    issue based on the language from the requests header

    :param request: self.request
    :return: uid
    """
    issue_uid = None
    try:
        issue_uid = request.json_body.get('issue')
    except (JSONDecodeError, AttributeError):
        pass
    if not issue_uid:
        issue_uid = request.matchdict.get('issue')
    if not issue_uid:
        issue_uid = request.params.get('issue')
    if not issue_uid:
        issue_uid = request.session.get('issue')

    # no issue found
    if not issue_uid:
        return None

    # save issue in session
    request.session['issue'] = issue_uid
    return issue_uid


def get_issue_based_on_header(request):
    """

    :param request:
    :return:
    """
    # logger('IssueHelper', 'get_issue_based_on_header', 'no saved issue found')
    ui_locales = get_language_from_header(request)
    db_issues = get_enabled_issues_as_query()
    db_lang = DBDiscussionSession.query(Language).filter_by(ui_locales=ui_locales).first()
    db_issue = db_issues.filter_by(lang_uid=db_lang.uid).first()
    if not db_issue:
        db_issue = db_issues.first()

    return db_issue.uid


def get_title_for_slug(slug) -> Optional[str]:
    """
    Returns the issues title for a given slug

    :param slug: String
    :return: String
    """
    db_issues = DBDiscussionSession.query(Issue).all()
    for issue in db_issues:
        if str(slugify(issue.title)) == str(slug):
            return issue.title
    return None


def get_issues_overview_for(db_user: User, app_url: str) -> Dict[str, Collection]:
    """
    Returns dictionary with keywords 'user' and 'others', which got lists with dicts with infos
    IMPORTANT: URL's are generated for the frontend!

    :param db_user: User
    :param app_url: current applications url
    :return: dict
    """

    if not db_user or db_user.nickname == nick_of_anonymous_user:
        return {
            'user': [],
            'other': []
        }

    if db_user.is_admin():
        db_issues_other_users = DBDiscussionSession.query(Issue).filter(Issue.author != db_user).all()
    else:
        db_issues_other_users = [issue for issue in db_user.accessible_issues if issue.author != db_user]

    db_issues_of_user = DBDiscussionSession.query(Issue).filter_by(author=db_user).order_by(
        Issue.uid.asc()).all()

    return {
        'user': [__create_issue_dict(issue, app_url) for issue in db_issues_of_user],
        'other': [__create_issue_dict(issue, app_url) for issue in db_issues_other_users]
    }


def get_issues_overview_on_start(db_user: User) -> dict:
    """
    Returns list with title, date, and count of statements for each visible issue

    :param db_user: User
    :return:
    """
    db_issues: List[Issue] = db_user.accessible_issues
    db_issues.sort(key=lambda issue: issue.uid)
    readable = []
    writable = []
    featured = []

    for index, db_issue in enumerate(db_issues):
        issue_dict = {
            'uid': db_issue.uid,
            'url': '/discuss/' + db_issue.slug,
            'statements': len(db_issue.statements),
            'active_users': get_number_of_authors(db_issue.uid),
            'title': db_issue.title,
            'date': db_issue.date.format('DD.MM.YY HH:mm'),
            'lang': {
                'is_de': db_issue.lang == 'de',
                'is_en': db_issue.lang == 'en',
            },
            'featured': db_issue.is_featured
        }
        if db_issue.is_read_only:
            readable.append(issue_dict)
        else:
            writable.append(issue_dict)

        if db_issue.is_featured:
            featured_issue_dict = copy.deepcopy(issue_dict)
            featured_issue_dict['info'] = db_issue.info
            featured.append(featured_issue_dict)

    return {
        'issues': {
            'readable': readable,
            'writable': writable,
            'featured': featured
        }
    }


def set_discussions_properties(db_user: User, db_issue: Issue, value, iproperty, translator) -> dict:
    """

    :param db_user: User
    :param db_issue: Issue
    :param value: The value which should be assigned to property
    :param iproperty: Property of Issue, e.g. is_disabled
    :param translator:
    :return:
    """
    if db_issue.author_uid != db_user.uid and not db_user.is_admin():
        return {'error': translator.get(_.noRights)}

    if iproperty == 'enable':
        db_issue.set_disabled(not value)
    elif iproperty == 'public':
        db_issue.set_private(not value)
    elif iproperty == 'writable':
        db_issue.set_read_only(not value)
    else:
        return {'error': translator.get(_.internalKeyError)}

    return {'error': ''}


def __create_issue_dict(db_issue: Issue, app_url: str) -> dict:
    """
    Returns dictionary with several information about the given issue

    :param db_issue: database row of issue
    :param app_url: current applications url
    :return: dict()
    """
    short_url_dict = generate_short_url(app_url + '/discuss/' + db_issue.slug)
    url = short_url_dict['url'] if len(short_url_dict['url']) > 0 else app_url + '/discuss/' + db_issue.slug

    # we do nto have to check for clicked arguments, cause arguments consist out of statements
    statements = [el.statement_uid for el in
                  DBDiscussionSession.query(StatementToIssue).filter_by(issue_uid=db_issue.uid).all()]
    db_clicked_statements = DBDiscussionSession.query(ClickedStatement).filter(
        ClickedStatement.statement_uid.in_(statements)).all()

    authors_clicked_statement = [click.author_uid for click in db_clicked_statements]
    db_authors_len = DBDiscussionSession.query(User).filter(User.uid.in_(authors_clicked_statement)).count()

    prepared_dict = {
        'uid': db_issue.uid,
        'title': db_issue.title,
        'url': '/' + db_issue.slug,
        'short_url': url,
        'date': db_issue.date.format('DD.MM.YY HH:mm'),
        'count_of_statements': len(statements),
        'is_enabled': not db_issue.is_disabled,
        'is_public': not db_issue.is_private,
        'is_writable': not db_issue.is_read_only,
        'participants': db_authors_len,
        'lang': {
            'is_de': db_issue.lang == 'de',
            'is_en': db_issue.lang == 'en',
        }
    }
    return prepared_dict

Provides helping function for issues. Prepares slug, info, argument count and the date of the issue as dict      :param db_issue: Issue     :param db_user: User     :return: Issue-dict() Returns number of active users for the issue      :param issue_uid: Issue Issue.uid     :return: Integer Creates an dictionary for the issue      :param db_issue: Issue     :param uid: current selected Issue.uid     :param lang: ui_locales     :return: dict() Returns the uid of the issue with given slug      :param slug: slug     :return: uid :param issue:     :param request:     :return: Returns issue uid saved in request. If there is no uid, we will choose an     issue based on the language from the requests header      :param request: self.request     :return: uid # no issue found # save issue in session :param request:     :return: # logger('IssueHelper', 'get_issue_based_on_header', 'no saved issue found') Returns the issues title for a given slug      :param slug: String     :return: String Returns dictionary with keywords 'user' and 'others', which got lists with dicts with infos     IMPORTANT: URL's are generated for the frontend!      :param db_user: User     :param app_url: current applications url     :return: dict Returns list with title, date, and count of statements for each visible issue      :param db_user: User     :return: :param db_user: User     :param db_issue: Issue     :param value: The value which should be assigned to property     :param iproperty: Property of Issue, e.g. is_disabled     :param translator:     :return: Returns dictionary with several information about the given issue      :param db_issue: database row of issue     :param app_url: current applications url     :return: dict() # we do nto have to check for clicked arguments, cause arguments consist out of statements