from datetime import timedelta
from tempfile import NamedTemporaryFile

from django.http import HttpResponse

from openpyxl import Workbook
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter

from .utils import format_d_m_Y

openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'


class OpenXMLExport:
    def __init__(self, sheet_title=None):
        self.wb = Workbook()
        self.ws = self.wb.active
        if sheet_title:
            self.ws.title = sheet_title
        self.bold = Font(name='Calibri', bold=True)
        self.row_idx = 1

    def write_line(self, values, bold=False, col_widths=()):
        # A values item can be an object with a `value` attribute
        for col_idx, value in enumerate(values, start=1):
            cell = self.ws.cell(row=self.row_idx, column=col_idx)
            if isinstance(value, timedelta):
                cell.number_format = '[h]:mm;@'
            elif hasattr(value, 'number_format'):
                cell.number_format = value.number_format
            cell.value = getattr(value, 'value', value)
            if bold:
                cell.font = self.bold
            if col_widths and len(col_widths) >= col_idx:
                self.ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1]
        self.row_idx += 1

    def add_sheet(self, title):
        self.wb.create_sheet(title)
        self.ws = self.wb[title]
        self.row_idx = 1

    def get_http_response(self, filename):
        with NamedTemporaryFile() as tmp:
            self.wb.save(tmp.name)
            tmp.seek(0)
            response = HttpResponse(tmp, content_type=openxml_contenttype)
            response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
        return response


class ExportReporting(OpenXMLExport):
    def __init__(self):
        super().__init__('temp')
        self.first_sheet = True
        # Totaux pour vérification interne dans les tests.
        self._total_spe = {'eval': timedelta(0), 'suivi': timedelta(0), 'gen': timedelta(0)}

    def setup_sheet(self, title):
        if not self.first_sheet:
            self.add_sheet(title)
        else:
            self.ws.title = title
        self.first_sheet = False

    def produce_suivis(self, sheet_title, query, mois):
        self.setup_sheet(sheet_title)
        SuiviSheet(self, mois).produce(query)

    def produce_nouveaux(self, sheet_title, query):
        self.setup_sheet(sheet_title)
        NouveauxSheet(self).produce(query)

    def produce_termines(self, sheet_title, query):
        self.setup_sheet(sheet_title)
        TerminesSheet(self).produce(query)

    def produce_totaux(self, sheet_title):
        self.setup_sheet(sheet_title)
        self.write_line(['Évaluation', self._total_spe['eval']], col_widths=[25, 10])
        self.write_line(['Accompagnement', self._total_spe['suivi']])
        self.write_line(['Intervention générale', self._total_spe['gen']])
        self.write_line(['Total', self._total_spe['eval'] + self._total_spe['suivi'] + self._total_spe['gen']])


class BaseFamilleSheet:
    en_tetes = [
        ('Institution', 12), ('Prestation', 10), ('Nom', 25), ('Prenom', 15), ('Genre', 8),
        ('Date de naissance', 17), ('Adresse', 20), ('NPA', 8), ('Localité', 20), ('Canton', 8),
        ('OPE', 25), ('Nom mère', 20), ('Prénom mère', 20), ('Nom père', 20), ('Prénom père', 20),
        ('Autorité parentale', 18), ('Statut marital', 15), ('Statut financier', 15),
        ('Fam. monopar.', 15), ('Nbre enfants', 12),
        ('Date demande', 15), ('Provenance', 15), ('Motif demande', 40), ('Début suivi', 15),
    ]

    def __init__(self, exp):
        self.exp = exp

    def _set_col_dimensions(self):
        for col_idx, (_, size) in enumerate(self.en_tetes, start=1):
            self.exp.ws.column_dimensions[get_column_letter(col_idx)].width = size

    def produce(self, query):
        self._set_col_dimensions()
        self.exp.write_line([et[0] for et in self.en_tetes], bold=True)
        for famille in query:
            self.produce_famille(famille)
        self.exp.ws.freeze_panes = self.exp.ws['A2']

    def produce_famille(self, famille):
        membres_suivis = famille.membres_suivis()
        if membres_suivis:
            for pers in famille.membres_suivis():
                data = self.collect_pers_data(famille, pers)
                self.exp.write_line(data)
        else:
            data = self.collect_pers_data(famille, None)
            self.exp.write_line(data)

    def collect_pers_data(self, famille, pers):
        parents = famille.parents()
        mere = next((par for par in parents if par.role.nom == 'Mère'), None)
        pere = next((par for par in parents if par.role.nom == 'Père'), None)
        return [
            'Fondation Transit',
            'AEMO',
            pers.nom if pers else famille.nom,
            pers.prenom if pers else "-",
            pers.genre if pers else "-",
            format_d_m_Y(pers.date_naissance) if pers else "-",
            pers.rue if pers else famille.rue,
            pers.npa if pers else famille.npa,
            pers.localite if pers else famille.localite,
            'NE',
            famille.suivi.ope_referent.nom_prenom if famille.suivi.ope_referent else '',
            mere.nom if mere else '',
            mere.prenom if mere else '',
            pere.nom if pere else '',
            pere.prenom if pere else '',
            famille.get_autorite_parentale_display(),
            famille.get_statut_marital_display(),
            famille.get_statut_financier_display(),
            {True: 'OUI', False: 'NON', None: ''}[famille.monoparentale],
            len(famille.membres_suivis()) + len(famille.enfants_non_suivis()),

            format_d_m_Y(famille.suivi.date_demande),
            famille.get_provenance_display(),
            famille.suivi.get_motif_demande_display(),
            format_d_m_Y(famille.suivi.date_debut_suivi),
        ]


class SuiviSheet(BaseFamilleSheet):
    en_tetes = BaseFamilleSheet.en_tetes + [('H. Évaluation', 12), ('H. Suivi', 12), ('H. Prest. gén.', 12)]

    def __init__(self, exp, date_debut_mois):
        self.date_debut_mois = date_debut_mois
        super().__init__(exp)

    def produce_famille(self, famille):
        # Prepare some data common to famille
        nb_membres_suivis = len(famille.membres_suivis()) or 1
        famille._h_evaluation_par_pers = famille.total_mensuel_evaluation(self.date_debut_mois) // nb_membres_suivis
        famille._h_suivi_par_pers = famille.total_mensuel_suivi(self.date_debut_mois) // nb_membres_suivis
        super().produce_famille(famille)

    def collect_pers_data(self, famille, pers):
        data = super().collect_pers_data(famille, pers)
        h_evaluation = famille._h_evaluation_par_pers
        h_suivi = famille._h_suivi_par_pers
        h_prest_gen = famille.prest_gen  # Annotation from the view.
        data.extend([h_evaluation, h_suivi, h_prest_gen])
        # Variables des totaux
        self.exp._total_spe['eval'] += h_evaluation
        self.exp._total_spe['suivi'] += h_suivi
        self.exp._total_spe['gen'] += h_prest_gen
        return data


class NouveauxSheet(BaseFamilleSheet):
    pass


class TerminesSheet(BaseFamilleSheet):
    en_tetes = BaseFamilleSheet.en_tetes + [
        ('Date fin suivi', 15), ('Motif fin suivi', 15), ('Destination', 15), ('Total heures', 12),
    ]

    def collect_pers_data(self, famille, pers):
        data = super().collect_pers_data(famille, pers)
        suivi = famille.suivi
        data.extend([
            format_d_m_Y(suivi.date_fin_suivi),
            famille.suivi.get_motif_fin_suivi_display(),
            famille.get_destination_display(),
            famille.temps_total_prestations_reparti(),
        ])
        return data


class ExportStatistique(OpenXMLExport):
    def __init__(self, *args, col_widths=None, **kwargs):
        self.col_widths = col_widths
        super().__init__(*args, **kwargs)

    def fill_data(self, generator):
        for row in generator:
            if row and row[0] == 'BOLD':
                self.write_line(row[1:], bold=True, col_widths=self.col_widths)
            else:
                self.write_line(row, col_widths=self.col_widths)
