from collections import defaultdict
from datetime import datetime
import logging
import random

from django.conf import settings
from lxml.builder import E

from casexml.apps.phone.fixtures import FixtureProvider
from corehq import toggles
from corehq.apps.app_manager.models import ReportModule
from corehq.apps.app_manager.suite_xml.features.mobile_ucr import is_valid_mobile_select_filter_type
from corehq.apps.userreports.reports.filters.factory import ReportFilterFactory
from corehq.util.xml_utils import serialize

from corehq.apps.userreports.const import UCR_SUPPORT_BOTH_BACKENDS
from corehq.apps.userreports.exceptions import UserReportsError, ReportConfigurationNotFoundError
from corehq.apps.userreports.models import get_report_config
from corehq.apps.userreports.reports.factory import ReportFactory
from corehq.apps.userreports.tasks import compare_ucr_dbs
from corehq.apps.app_manager.dbaccessors import get_apps_in_domain

MOBILE_UCR_RANDOM_THRESHOLD = 100


def _should_sync(restore_state):
    last_sync_log = restore_state.last_sync_log
    if not last_sync_log or restore_state.overwrite_cache:
        return True

    sync_interval = restore_state.restore_user.get_mobile_ucr_sync_interval()
    return (
        not last_sync_log or
        not sync_interval or
        (datetime.utcnow() - last_sync_log.date).total_seconds() > sync_interval
    )



class ReportFixturesProvider(FixtureProvider):
    id = 'commcare:reports'

    def __call__(self, restore_state):
        """
        Generates a report fixture for mobile that can be used by a report module
        """
        restore_user = restore_state.restore_user
        if not toggles.MOBILE_UCR.enabled(restore_user.domain) or not _should_sync(restore_state):
            return []

        app = restore_state.params.app
        apps = [app] if app else [a for a in get_apps_in_domain(restore_user.domain, include_remote=False)]
        report_configs = [
            report_config
            for app_ in apps
            for module in app_.modules if isinstance(module, ReportModule)
            for report_config in module.report_configs
        ]
        if not report_configs:
            return []

        root = E.fixture(id=self.id, user_id=restore_user.user_id)
        reports_elem = E.reports(last_sync=datetime.utcnow().isoformat())
        for report_config in report_configs:
            try:
                reports_elem.append(self.report_config_to_fixture(report_config, restore_user))
            except ReportConfigurationNotFoundError as err:
                logging.exception('Error generating report fixture: {}'.format(err))
                continue
            except UserReportsError:
                if settings.UNIT_TESTING or settings.DEBUG:
                    raise
            except Exception as err:
                logging.exception('Error generating report fixture: {}'.format(err))
                if settings.UNIT_TESTING or settings.DEBUG:
                    raise
        root.append(reports_elem)
        return [root]

    @staticmethod
    def report_config_to_fixture(report_config, restore_user):
        domain = restore_user.domain
        report, data_source = ReportFixturesProvider._get_report_and_data_source(
            report_config.report_id, domain
        )

        # TODO: Convert to be compatible with restore_user
        # apply filters specified in report module
        all_filter_values = {
            filter_slug: restore_user.get_ucr_filter_value(filter, report.get_ui_filter(filter_slug))
            for filter_slug, filter in report_config.filters.items()
        }
        # apply all prefilters
        prefilters = [ReportFilterFactory.from_spec(p, report) for p in report.prefilters]
        prefilter_values = {prefilter.name: prefilter.value() for prefilter in prefilters}
        all_filter_values.update(prefilter_values)
        # filter out nulls
        filter_values = {
            filter_slug: filter_value for filter_slug, filter_value in all_filter_values.items()
            if filter_value is not None
        }
        defer_filters = {
            filter_slug: report.get_ui_filter(filter_slug)
            for filter_slug, filter_value in all_filter_values.items()
            if filter_value is None and is_valid_mobile_select_filter_type(report.get_ui_filter(filter_slug))
        }
        data_source.set_filter_values(filter_values)
        data_source.defer_filters(defer_filters)
        filter_options_by_field = defaultdict(set)

        rows_elem = ReportFixturesProvider._get_report_elem(
            data_source,
            {ui_filter.field for ui_filter in defer_filters.values()},
            filter_options_by_field
        )
        filters_elem = ReportFixturesProvider._get_filters_elem(
            defer_filters, filter_options_by_field, restore_user._couch_user)

        if (data_source.config.backend_id in UCR_SUPPORT_BOTH_BACKENDS and
                random.randint(0, MOBILE_UCR_RANDOM_THRESHOLD) == MOBILE_UCR_RANDOM_THRESHOLD):
            compare_ucr_dbs.delay(domain, report.report_id, filter_values)

        report_elem = E.report(id=report_config.uuid, report_id=report_config.report_id)
        report_elem.append(filters_elem)
        report_elem.append(rows_elem)
        return report_elem

    @staticmethod
    def _get_report_and_data_source(report_id, domain):
        report = get_report_config(report_id, domain)[0]
        data_source = ReportFactory.from_spec(report, include_prefilters=True)
        return report, data_source

    @staticmethod
    def _get_filters_elem(defer_filters, filter_options_by_field, couch_user):
        filters_elem = E.filters()
        for filter_slug, ui_filter in defer_filters.items():
            # @field is maybe a bad name for this attribute,
            # since it's actually the filter slug
            filter_elem = E.filter(field=filter_slug)
            option_values = filter_options_by_field[ui_filter.field]
            choices = ui_filter.choice_provider.get_sorted_choices_for_values(option_values, couch_user)
            for choice in choices:
                # add the correct text from ui_filter.choice_provider
                option_elem = E.option(choice.display, value=choice.value)
                filter_elem.append(option_elem)
            filters_elem.append(filter_elem)
        return filters_elem

    @staticmethod
    def _get_report_elem(data_source, deferred_fields, filter_options_by_field):
        def _row_to_row_elem(row, index, is_total_row=False):
            row_elem = E.row(index=str(index), is_total_row=str(is_total_row))
            for k in sorted(row.keys()):
                value = serialize(row[k])
                row_elem.append(E.column(value, id=k))
                if not is_total_row and k in deferred_fields:
                    filter_options_by_field[k].add(value)
            return row_elem

        rows_elem = E.rows()
        for i, row in enumerate(data_source.get_data()):
            rows_elem.append(_row_to_row_elem(row, i))
        if data_source.has_total_row:
            total_row = data_source.get_total_row()
            rows_elem.append(_row_to_row_elem(
                dict(
                    zip(
                        map(lambda column_config: column_config.column_id, data_source.top_level_columns),
                        map(str, total_row)
                    )
                ),
                data_source.get_total_records(),
                is_total_row=True,
            ))
        return rows_elem


report_fixture_generator = ReportFixturesProvider()

@@ -967,16 +967,18 @@
 LD = 100
+00
 %0A%0A%0Adef _
@@ -5081,16 +5081,23 @@
 , report
+_config
 .report_

fix comparing mobile ucrs