"""Reporting statistics and data module"""
from collections import defaultdict
from datetime import datetime
from smtplib import SMTPRecipientsRefused

from flask import current_app
from flask_babel import force_locale
from werkzeug.exceptions import Unauthorized

from ..audit import auditable_event
from ..cache import cache
from ..date_tools import FHIR_datetime
from ..trigger_states.models import TriggerStatesReporting
from .app_text import MailResource, SiteSummaryEmail_ATMA, app_text
from .communication import load_template_args
from .message import EmailMessage
from .organization import Organization, OrgTree
from .overall_status import OverallStatus
from .questionnaire_response import aggregate_responses
from .qb_status import QB_Status
from .qb_timeline import qb_status_visit_name
from .questionnaire_bank import visit_name
from .questionnaire_response import (
    QNR_results,
    qnr_csv_column_headers,
    generate_qnr_csv,
)
from .research_study import EMPRO_RS_ID, ResearchStudy
from .role import ROLE, Role
from .user import User, UserRoles, patients_query
from .user_consent import consent_withdrawal_dates


def adherence_report(
        requested_as_of_date, acting_user_id, include_test_role, org_id,
        research_study_id, response_format, lock_key, celery_task, limit):
    """Generates the adherence report

    Designed to be executed in a background task - all inputs and outputs are
    easily serialized (executing celery_task parent an obvious exception).

    :param requested_as_of_date: string form of as_of_date, or None to use now
    :param acting_user_id: id of user evoking request, for permission check
    :param include_test_role: set to include test patients in results
    :param org_id: set to limit to patients belonging to a branch of org tree
    :param research_study_id: research study to report on
    :param response_format: 'json' or 'csv'
    :param lock_key: name of TimeoutLock key used to throttle requests
    :param celery_task: used to update status when run as a celery task
    :param limit: limit run to first 'n' patients - used only by testing API
    :return: dictionary of results, easily stored as a task output, including
       any details needed to assist the view method

    """
    acting_user = User.query.get(acting_user_id)
    if requested_as_of_date:
        as_of_date = FHIR_datetime.parse(requested_as_of_date)
    else:
        as_of_date = datetime.utcnow()

    # If limited by org - use org and its children as filter list
    requested_orgs = (
        OrgTree().here_and_below_id(organization_id=org_id) if org_id
        else None)

    patients = patients_query(
        acting_user=acting_user,
        include_test_role=include_test_role,
        requested_orgs=requested_orgs)
    data = []
    current, total = 0, patients.count()
    for patient in patients:

        # occasionally update the celery task status if defined
        current += 1
        if limit:
            total = limit
            if current > limit:
                break

        if not current % 25 and celery_task:
            celery_task.update_state(
                state='PROGRESS', meta={'current': current, 'total': total})

        if research_study_id not in ResearchStudy.assigned_to(patient):
            continue

        try:
            acting_user.check_role('view', other_id=patient.id)
        except Unauthorized:
            # simply exclude any patients the user can't view
            continue

        qb_stats = QB_Status(
            user=patient,
            research_study_id=research_study_id,
            as_of_date=as_of_date)
        row = {
            'user_id': patient.id,
            'country': patient.organizations[0].country,
            'site': patient.organizations[0].name,
            'status': str(qb_stats.overall_status)}

        c_date, w_date = consent_withdrawal_dates(
                user=patient, research_study_id=research_study_id)
        consent = c_date if c_date else w_date
        row['consent'] = FHIR_datetime.as_fhir(consent)

        study_id = patient.external_study_id
        if study_id:
            row['study_id'] = study_id

        # if no current, try previous (as current may be expired)
        last_viable = qb_stats.current_qbd(
            even_if_withdrawn=True) or qb_stats.prev_qbd
        if last_viable:
            row['qb'] = last_viable.questionnaire_bank.name
            row['visit'] = visit_name(last_viable)
            if row['status'] == 'Completed':
                row['completion_date'] = FHIR_datetime.as_fhir(
                    last_viable.completed_date(patient.id))
            entry_method = QNR_results(
                patient,
                research_study_id=research_study_id,
                qb_ids=[last_viable.qb_id],
                qb_iteration=last_viable.iteration).entry_method()
            if entry_method:
                row['entry_method'] = entry_method

            if research_study_id == EMPRO_RS_ID:
                # Initialize trigger states reporting for patient
                ts_reporting = TriggerStatesReporting(patient_id=patient.id)

                # Add clinician and trigger data for EMPRO reports
                if len(patient.clinicians) > 0:
                    row['clinician'] = ';'.join(
                        clinician.display_name for clinician in
                        patient.clinicians)
                # Rename column header for EMPRO
                if 'completion_date' in row:
                    row['EMPRO_questionnaire_completion_date'] = (
                        row.pop('completion_date'))

                # Correct for zero index visit month in db
                visit_month = int(row['visit'].split()[-1]) - 1
                t_status = ts_reporting.latest_action_state(visit_month)
                row['clinician_status'] = (
                    t_status.title() if t_status else '')
                ht = ts_reporting.hard_triggers_for_visit(visit_month)
                row['hard_trigger_domains'] = ', '.join(ht) if ht else ''
                st = ts_reporting.soft_triggers_for_visit(visit_month)
                row['soft_trigger_domains'] = ', '.join(st) if st else ''
                da = ts_reporting.domains_accessed(visit_month)
                row['content_domains_accessed'] = ', '.join(da) if da else ''

        data.append(row)

        # as we require a full history, continue to add rows for each previous
        # visit available
        for qbd, status in qb_stats.older_qbds(last_viable):
            historic = row.copy()
            historic['status'] = status
            historic['qb'] = qbd.questionnaire_bank.name
            historic['visit'] = visit_name(qbd)
            historic['completion_date'] = (
                FHIR_datetime.as_fhir(qbd.completed_date(patient.id))
                if status == 'Completed' else '')
            entry_method = QNR_results(
                patient,
                research_study_id=research_study_id,
                qb_ids=[qbd.qb_id],
                qb_iteration=qbd.iteration).entry_method()
            if entry_method:
                historic['entry_method'] = entry_method
            else:
                historic.pop('entry_method', None)

            if research_study_id == EMPRO_RS_ID:
                # Correct for zero index visit month in db
                visit_month = int(historic['visit'].split()[-1]) - 1
                t_status = ts_reporting.latest_action_state(visit_month)
                historic['clinician_status'] = (
                    t_status.title() if t_status else '')
                ht = ts_reporting.hard_triggers_for_visit(visit_month)
                historic['hard_trigger_domains'] = ', '.join(ht) if ht else ''
                st = ts_reporting.soft_triggers_for_visit(visit_month)
                historic['soft_trigger_domains'] = ', '.join(st) if st else ''
                da = ts_reporting.domains_accessed(visit_month)
                historic['content_domains_accessed'] = (
                    ', '.join(da) if da else '')

                # Rename column header for EMPRO
                if 'completion_date' in historic:
                    historic['EMPRO_questionnaire_completion_date'] = (
                        historic.pop('completion_date'))
            data.append(historic)

        # if user is eligible for indefinite QB, add status
        qbd, status = qb_stats.indef_status()
        if qbd:
            indef = row.copy()
            indef['status'] = status
            # Indefinite doesn't have a row in the timeline, look
            # up matching date from QNRs
            indef['completion_date'] = (
                FHIR_datetime.as_fhir(qbd.completed_date(patient.id))
                if status == 'Completed' else '')
            indef['qb'] = qbd.questionnaire_bank.name
            indef['visit'] = "Indefinite"
            entry_method = QNR_results(
                patient,
                research_study_id=research_study_id,
                qb_ids=[qbd.qb_id],
                qb_iteration=qbd.iteration).entry_method()
            if entry_method:
                indef['entry_method'] = entry_method
            else:
                indef.pop('entry_method', None)
            data.append(indef)

    results = {
        'data': data,
        'lock_key': lock_key,
        'response_format': response_format,
        'required_user_id': acting_user_id}
    if response_format == 'csv':
        base_name = 'Questionnaire-Timeline-Data'
        if org_id:
            base_name = '{}-{}'.format(
                base_name,
                Organization.query.get(org_id).name.replace(' ', '-'))
        results['filename_prefix'] = base_name
        results['column_headers'] = [
            'user_id', 'study_id', 'status', 'visit', 'entry_method',
            'country', 'site', 'consent', 'completion_date']
        if research_study_id == EMPRO_RS_ID:
            results['column_headers'] = [
                'user_id',
                'study_id',
                'country',
                'site',
                'visit',
                'status',
                'EMPRO_questionnaire_completion_date',
                'soft_trigger_domains',
                'hard_trigger_domains',
                'content_domains_accessed',
                'clinician',
                'clinician_status',
                ]

    return results


def research_report(