# coding: utf-8

import csv
import io
import zipfile
from datetime import datetime

import xlwt
from django.conf import settings
from django.utils.encoding import force_bytes, force_text
from django.utils import timezone
from django.utils import translation
from django.utils.translation import ugettext_lazy as _

from idm.celery_app import app
from idm.core.constants.role import ROLE_STATE
from idm.framework.task import BaseTask, UnrecoverableError
from idm.notification.utils import send_notification
from idm.reports.datagrids import ActionLogGrid, RolesReportGrid
from idm.users.models import User
from idm.utils.queryset import queryset_iterator
from idm.utils.tasks import get_object_or_fail_task

IDM_ROLES_REPORT_FIELDS = ('owner', 'owner_username', 'group_type', 'position', 'department_group', 'system', 'role',
                           'role_code', 'merged_data', 'state', 'granted_at', 'approvers')
IDM_ACTION_REPORT_FIELDS = ('added', 'requester', 'requester_username', 'action', 'system', 'role', 'subject',
                            'fields_data', 'comment', 'extra_info')
IDM_XLS_MAX_CELL_LENGTH = 32767
CHUNK_SIZE = 5000


def make_report(queryset, format_, type_, requester, comment=''):
    assert type_ in ('roles', 'actions')

    export_class = {'xls': CommonXLSCreator, 'csv': CommonCSVCreator}[format_]
    export_class.delay(
        format_=format_,
        type_=type_,
        model=queryset.model,
        query=queryset.query,
        prefetch_lookups=queryset._prefetch_related_lookups,
        username=requester.username,
        comment=comment,
    )


class CSVDialect(csv.excel):
    delimiter = ';'


class CommonReportCreator(BaseTask):
    """Для создания письма отчета с аттачем в формате CSV"""
    max_retries = 0
    monitor_success = False  # таски этого типа запускаются ad-hoc

    def render_field_state(self, obj):
        """Обрабатывает поле state"""
        return ROLE_STATE.STATE_CHOICES[obj.state]

    def render_field_group_type(self, obj):
        return obj.group.type if obj.group else '-'

    def render_field_position(self, obj):
        return obj.user.position if obj.user else '-'

    def render_field_department_group(self, obj):
        return obj.user.department_group.name if obj.user and obj.user.department_group_id is not None else '-'

    def _failed(self, exc_val, kwargs, retry=False):
        username = kwargs['username']
        user = get_object_or_fail_task(User, username=username)
        with translation.override(user.lang_ui):
            send_notification(_('Ошибка формирования отчёта.'), ['emails/reports/report_error.txt'], [user], {
                'comment': kwargs.get('comment', None),
                'message': force_text(exc_val)
            }, cc=settings.EMAILS_FOR_PROBLEMS)

    def init(self, format_, type_, model, query, prefetch_lookups, username, **kwargs):
        self.log.info('Make %s report for %s', self.__class__.__name__, username)
        comment = kwargs.get('comment', '')
        user = get_object_or_fail_task(User, username=username)
        user.actions.create(
            action='report_requested',
            requester=user,
            data={'comment': comment},
        )

        io_class = {'xls': io.BytesIO, 'csv': io.StringIO}[format_]

        if type_ == 'roles':
            fields = IDM_ROLES_REPORT_FIELDS
            grid_class = RolesReportGrid
        else:
            fields = IDM_ACTION_REPORT_FIELDS
            grid_class = ActionLogGrid

        with translation.override(user.lang_ui), timezone.override(user.timezone):
            queryset = model.objects.all()
            queryset.query = query
            queryset._prefetch_related_lookups = prefetch_lookups

            limits = {
                'xls': {
                    'limit': xlwt.ExcelMagic.MAX_ROW,
                    'error': _('Количество строк в отчёте больше ограничения xls файла (%s строк). '
                               'Пожалуйста, выберите другой формат или сократите выборку.')
                },
                'csv': {
                    'limit': settings.IDM_REPORTS_CSV_MAX_ROWS,
                    'error': _('Количество строк в отчёте больше максимально допустимого (%%s строк). '
                               'Пожалуйста, сократите выборку или напишите на %s, '
                               'чтобы получить полный отчёт.') % settings.IDM_MAIL_LIST
                }
            }
            count = queryset.count()
            limit = limits[format_]['limit']
            error = limits[format_]['error']

            if count >= limit:
                raise UnrecoverableError(force_text(error % limit))

            self.log.info('Get data for %s: %d rows', username, count)
            datagrid = grid_class()
            columns = datagrid.columns

            renderers = {
                field: getattr(self, 'render_field_%s' % field, columns[field].render_data)
                for field in fields
            }

            self.log.info('Build report for %s: %d items', username, count)
            with io_class() as report_file:
                self.make_report(fields, columns, queryset, renderers, report_file)
                report = report_file.getvalue()

            report_archive = io.BytesIO()
            filename = 'report_%s.%s' % (datetime.now().strftime('%y%m%d_%H%M%S'), self.extension)
            archive = zipfile.ZipFile(report_archive, 'w', zipfile.ZIP_DEFLATED)
            archive.writestr(filename, report)
            archive.close()
            content = report_archive.getvalue()

            self.log.info('Send report to %s', username)
            send_notification(_('Отчёт сформирован.'), ['emails/reports/report_attach.txt'], [user], {
                'comment': comment
            }, attachments=[(filename + '.zip', content, 'application/zip')])

            self.log.info('Report to %s is finished', username)


class CommonCSVCreator(CommonReportCreator):
    """Для создания отчета в формате CSV"""
    extension = 'csv'

    def make_report(self, fields, columns, queryset, renderers, report_file):
        writer = csv.writer(report_file, dialect=CSVDialect)
        writer.writerow([columns[field].label for field in fields])
        writer.writerow([])

        chunk_iter = queryset_iterator(queryset, CHUNK_SIZE)
        for obj in chunk_iter:
            rowdata = [renderers[field](obj) for field in fields]
            row = [force_text(item) for item in rowdata]
            writer.writerow(row)


class CommonXLSCreator(CommonReportCreator):
    """Для создания отчета в формате XLS"""
    extension = 'xls'

    def make_report(self, fields, columns, queryset, renderers, report_file):
        wbk = xlwt.Workbook()
        sheet = wbk.add_sheet('sheet')

        for i, header in enumerate([force_text(columns[field].label) for field in fields]):
            sheet.write(0, i, header)

        chunk_iter = queryset_iterator(queryset, CHUNK_SIZE)
        for x, obj in enumerate(chunk_iter):
            for y, field in enumerate(fields):
                cell = force_text(renderers[field](obj))
                sheet.write(x + 1, y, cell[:IDM_XLS_MAX_CELL_LENGTH])

        wbk.save(report_file)


CommonCSVCreator = app.register_task(CommonCSVCreator())
CommonXLSCreator = app.register_task(CommonXLSCreator())
