from __future__ import absolute_import, unicode_literals

import csv

import django
import six
from django.http.response import HttpResponse
from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
from openpyxl.writer.write_only import WriteOnlyCell


if django.VERSION >= (1, 9):
    from django.db.models.query import QuerySet
else:
    from django.db.models.query import QuerySet, ValuesQuerySet


ROW_LIMIT = 1048576
COL_LIMIT = 16384


class ExcelResponse(HttpResponse):
    """
    This class provides an HTTP Response in the form of an Excel spreadsheet, or CSV file.
    """

    def __init__(self, data, output_filename='excel_data', worksheet_name=None, force_csv=False, header_font=None,
                 data_font=None, guess_types=True, *args, **kwargs):
        # We do not initialize this with streaming_content, as that gets generated when needed
        self.output_filename = output_filename
        self.worksheet_name = worksheet_name or 'Sheet 1'
        self.header_font = header_font
        self.data_font = data_font
        self.force_csv = force_csv
        self.guess_types = guess_types
        super(ExcelResponse, self).__init__(data, *args, **kwargs)

    @property
    def content(self):
        return b''.join(self._container)

    @content.setter
    def content(self, value):
        workbook = None
        if not bool(value) or not len(value):  # Short-circuit to protect against empty querysets/empty lists/None, etc
            self._container = []
            return
        elif isinstance(value, list):
            workbook = self._serialize_list(value)
        elif isinstance(value, QuerySet):
            workbook = self._serialize_queryset(value)
        if django.VERSION < (1, 9):
            if isinstance(value, ValuesQuerySet):
                workbook = self._serialize_values_queryset(value)
        if workbook is None:
            raise ValueError('ExcelResponse accepts the following data types: list, dict, QuerySet, ValuesQuerySet')

        if self.force_csv:
            self['Content-Type'] = 'text/csv; charset=utf8'
            self['Content-Disposition'] = 'attachment;filename="{}.csv"'.format(self.output_filename)
            workbook.seek(0)
            workbook = self.make_bytes(workbook.getvalue())
        else:
            self['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
            self['Content-Disposition'] = 'attachment; filename="{}.xlsx"'.format(self.output_filename)
            workbook = save_virtual_workbook(workbook)
        self._container = [self.make_bytes(workbook)]

    def _serialize_list(self, data):
        if isinstance(data[0], dict):  # If we're dealing with a list of dictionaries, generate the headers
            headers = [key for key in data[0]]
        else:
            headers = data[0]
        if len(data) > ROW_LIMIT or len(headers) > COL_LIMIT or self.force_csv:
            self.force_csv = True
            workbook = six.StringIO()
            csvwriter = csv.writer(workbook, dialect='excel')
            append = getattr(csvwriter, 'writerow')
            write_header = append
        else:
            workbook = Workbook(write_only=True)
            workbook.guess_types = self.guess_types
            worksheet = workbook.create_sheet(title=self.worksheet_name)

            # Define custom functions for appending so that we can handle any formatting
            def append(data):
                return self._append_excel_row(worksheet, data, header=False)

            def write_header(data):
                return self._append_excel_row(worksheet, data, header=True)

        if isinstance(data[0], dict):
            append(headers)
        for index, row in enumerate(data):
            if isinstance(row, dict):
                write_header([row.get(col, None) for col in headers])
            else:
                if index > 0:
                    append(row)
                else:
                    write_header(row)
        return workbook

    def _serialize_queryset(self, data):
        if isinstance(data[0], dict):  # .values() returns a list of dicts
            return self._serialize_list(list(data))
        else:
            return self._serialize_list(list(data.values()))

    def _serialize_values_queryset(self, data):
        return self._serialize_list(list(data))

    def _append_excel_row(self, worksheet, data, header=False):
        if header:
            font = self.header_font
        else:
            font = self.data_font

        if not font:
            row = data
        else:
            row = []
            for cell in data:
                cell = WriteOnlyCell(worksheet, cell)
                cell.font = font
                row.append(cell)

        worksheet.append(row)