import csv
from io import BytesIO

import pandas as pd
from datetime import date
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db.models import Q, Sum
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.template.loader import render_to_string
from django.views import generic
from weasyprint import CSS, HTML
from weasyprint.fonts import FontConfiguration

from .forms import AreaForm, BuildingForm, CertificateForm, CompanyForm, EnergyForm
from .models import Area, AreaDocument, Building, Certificate, Company, Energy, EnergyDocument
from .utils.utils import clean_number


def home_view(request):
    context = {
        'building_count': Building.objects.count(),
        'certificates_count': Certificate.objects.count(),
        'energy_performance_sum': clean_number(
            Certificate.objects.aggregate(Sum('energy_performance'))['energy_performance__sum']),
        'nett_floor_area_sum': clean_number(Area.objects.aggregate(Sum('nett_floor_area'))['nett_floor_area__sum']),

        # Data for Energy Consumption chart
        'grid_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.grid).aggregate(Sum('consumption'))['consumption__sum']),
        'renewable_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.renewable).aggregate(Sum('consumption'))['consumption__sum']),
        'liquid_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.liquid).aggregate(Sum('consumption'))['consumption__sum']),
        'solid_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.solid).aggregate(Sum('consumption'))['consumption__sum']),
        'gas_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.gas).aggregate(Sum('consumption'))['consumption__sum']),
        'other_energy_sum': clean_number(
            Energy.objects.filter(source=Energy.Sources.other).aggregate(Sum('consumption'))['consumption__sum']),

        # Data for Occupancy chart
        'a1_occupancy': clean_number(Building.objects.filter(occupancy_class=Building.OccupancyClass.a1).count()),
        'a2_occupancy': clean_number(Building.objects.filter(occupancy_class=Building.OccupancyClass.a2).count()),
        'a3_occupancy': clean_number(Building.objects.filter(occupancy_class=Building.OccupancyClass.a3).count()),
        'g1_occupancy': clean_number(Building.objects.filter(occupancy_class=Building.OccupancyClass.g1).count()),
        'other_occupancy': clean_number(
            Building.objects.filter(
                Q(occupancy_class=Building.OccupancyClass.a4) |
                Q(occupancy_class=Building.OccupancyClass.f1) |
                Q(occupancy_class=Building.OccupancyClass.h1)
            ).count()
        ),

        # Todo: GHG calculator should be added to models and this should be corrected thereafter
        'ghg_scope_1': clean_number(
            Energy.objects.filter(source=Energy.Sources.gas).aggregate(Sum('consumption'))['consumption__sum']
        ) * 0.035417 + clean_number(
            Energy.objects.filter(source=Energy.Sources.solid).aggregate(Sum('consumption'))['consumption__sum']
        ) * 0.042763 + clean_number(
            Energy.objects.filter(source=Energy.Sources.liquid).aggregate(Sum('consumption'))['consumption__sum']
        ) * 0.040330,

        'ghg_scope_2': clean_number(
            Energy.objects.filter(source=Energy.Sources.grid).aggregate(Sum('consumption'))['consumption__sum']
        ) * 0.94 / 1000,

        'ghg_emissions_sum': round(
            clean_number(
                Energy.objects.filter(source=Energy.Sources.gas).aggregate(Sum('consumption'))['consumption__sum']
            ) * 0.035417 +
            clean_number(
                Energy.objects.filter(source=Energy.Sources.solid).aggregate(Sum('consumption'))['consumption__sum']
            ) * 0.042763 +
            clean_number(
                Energy.objects.filter(source=Energy.Sources.liquid).aggregate(Sum('consumption'))['consumption__sum']
            ) * 0.040330 +
            clean_number(
                Energy.objects.filter(source=Energy.Sources.grid).aggregate(Sum('consumption'))['consumption__sum']
            ) * 0.94 / 1000, 3
        ),

        # % Monthly increases
        'building_count_inc': Building.monthly_increase(),
        'certificate_count_inc': Certificate.monthly_increase(),
        'energy_usage_inc': Certificate.monthly_energy_increase(),
        'renewable_usage_inc': Energy.monthly_increase(Energy.Sources.renewable),
        'net_floor_area_inc': Area.monthly_increase(),
        'emissions_inc': Energy.monthly_emissions_increase(),

    }
    return render(request, 'pages/home.html', context)


class CompanyListView(LoginRequiredMixin, generic.ListView):
    model = Company
    context_object_name = 'company_list'
    template_name = 'company/index.html'
    paginate_by = 10

    def get_paginate_by(self, queryset):
        return self.request.GET.get('paginate_by', self.paginate_by)


@login_required()
def company_detail_view(request, pk):
    company = get_object_or_404(Company, id=pk)
    buildings = company.building_set.all()

    if len(buildings) > 10:
        paginator = Paginator(buildings, 10)
        page_number = request.GET.get('page')
        page_obj = paginator.get_page(page_number)
        buildings = page_obj.object_list
        is_paginated = paginator.num_pages > 1
    else:
        paginator = None
        page_obj = None
        is_paginated = False

    context = {
        'company': company,
        'buildings': buildings,
        'paginator': paginator,
        'page_obj': page_obj,
        'is_paginated': is_paginated,
    }
    return render(request, 'company/detail.html', context)


class CompanyCreateView(LoginRequiredMixin, generic.edit.CreateView):
    form_class = CompanyForm
    model = Company
    template_name = 'company/create.html'

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.creator = self.request.user
        self.object.save()
        return redirect('company_detail', pk=self.object.pk)


class CompanyEditView(LoginRequiredMixin, generic.edit.UpdateView):
    form_class = CompanyForm
    model = Company
    template_name = 'company/edit.html'


class BuildingListView(LoginRequiredMixin, generic.ListView):
    model = Building
    context_object_name = 'building_list'
    template_name = 'building/index.html'
    paginate_by = 10


class BuildingDetailView(LoginRequiredMixin, generic.DetailView):
    model = Building
    template_name = 'building/detail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object_ = context['object']
        context['areas'] = object_.area_set.all().order_by('-date')
        context['energy_sources'] = object_.energy_set.all().order_by('-date')
        context['certificates'] = object_.certificate_set.all().order_by('-end_date')
        return context

    def post(self, request, pk=None):
        if 'generate' in request.POST:
            building = Building.objects.get(id=pk)
            building.unique_building_code = building.generate_building_code()
            building.save()
        return redirect('building_detail', pk=pk)


@login_required()
def building_create_view(request, pk_company):
    company = get_object_or_404(Company, id=pk_company)
    form = BuildingForm(request.POST or None)

    if form.is_valid():
        building = form.save(commit=False)
        building.creator = request.user
        building.unique_building_code = building.generate_building_code()
        building.save()
        return redirect('building_detail', pk=building.pk)

    context = {
        'form': form,
        'company': company,
    }
    return render(request, 'building/create.html', context)


class BuildingEditView(LoginRequiredMixin, generic.edit.UpdateView):
    form_class = BuildingForm
    model = Building
    template_name = 'building/edit.html'


def set_area_calc_values(area: Area):
    if area.excluded_floor_area is None:
        area.excluded_floor_area = 0
    else:
        area.excluded_floor_area = abs(area.excluded_floor_area)
    if area.nett_floor_area is None and area.total_floor_area is not None:
        area.nett_floor_area = area.total_floor_area - area.excluded_floor_area
    if area.nett_floor_area is not None and area.total_floor_area is None:
        area.total_floor_area = area.nett_floor_area + area.excluded_floor_area


@login_required()
def building_area_create_view(request, pk_building):
    building = get_object_or_404(Building, id=pk_building)
    form = AreaForm(request.POST or None, request.FILES or None)

    if form.is_valid():
        area = form.save(commit=False)
        set_area_calc_values(area)
        area.creator = request.user
        area.save()

        for file in request.FILES.getlist('supporting_doc'):
            instance = AreaDocument(link=area, supporting_doc=file)
            instance.save()

        area.save()
        return redirect('building_detail', pk=building.pk)

    context = {'form': form, 'building': building}
    return render(request, 'floor_plan/create.html', context)


class BuildingAreaEditView(LoginRequiredMixin, generic.edit.UpdateView):
    form_class = AreaForm
    model = Area

    template_name = 'floor_plan/edit.html'

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return reverse('building_detail', args=[self.object.building.pk])

        self.object = self.get_object()
        form = AreaForm(request.POST or None, request.FILES or None, instance=self.object)

        if form.is_valid():
            area = form.save(commit=False)
            set_area_calc_values(area)
            area.creator = request.user

            for file in request.FILES.getlist('supporting_doc'):
                instance = AreaDocument(link=area, supporting_doc=file)
                instance.save()

            return redirect('building_detail', pk=self.object.building.pk)

        return reverse('building_detail', args=[self.object.building.pk])

    def get_success_url(self):
        return reverse('building_detail', args=[self.object.building.pk])

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object_ = context['object']
        context['supporting_docs'] = object_.areadocument_set.all()
        return context


class BuildingAreaDeleteView(LoginRequiredMixin, generic.edit.DeleteView):
    model = Area

    def get_success_url(self):
        return reverse('building_detail', args=[self.object.building.pk])


@login_required()
def building_energy_create_view(request, pk_building):
    building = get_object_or_404(Building, id=pk_building)
    form = EnergyForm(request.POST or None, request.FILES or None)

    if form.is_valid():
        energy = form.save(commit=False)
        energy.creator = request.user
        energy.save()

        for file in request.FILES.getlist('supporting_doc'):
            instance = EnergyDocument(link=energy, supporting_doc=file)
            instance.save()

        energy.save()
        return redirect('building_detail', pk=building.pk)

    context = {
        'form': form,
        'building': building,
    }
    return render(request, 'energy/create.html', context)


class BuildingEnergyEditView(LoginRequiredMixin, generic.edit.UpdateView):
    form_class = EnergyForm
    model = Energy
    template_name = 'energy/edit.html'

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return reverse('building_detail', args=[self.object.building.pk])

        self.object = self.get_object()
        form = EnergyForm(request.POST or None, request.FILES or None, instance=self.object)

        if form.is_valid():
            energy = form.save(commit=False)
            energy.creator = request.user

            for file in request.FILES.getlist('supporting_doc'):
                instance = EnergyDocument(link=energy, supporting_doc=file)
                instance.save()

            return redirect('building_detail', pk=self.object.building.pk)

        return reverse('building_detail', args=[self.object.building.pk])

    def get_success_url(self):
        return reverse('building_detail', args=[self.object.building.pk])

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object_ = context['object']
        context['supporting_docs'] = object_.energydocument_set.all()
        return context


class BuildingEnergyDeleteView(LoginRequiredMixin, generic.edit.DeleteView):
    model = Energy

    def get_success_url(self):
        return reverse('building_detail', args=[self.object.building.pk])


class CertificateView(LoginRequiredMixin, generic.DetailView):
    model = Certificate
    template_name = 'certificate/certificate.html'


class CertificateDeleteView(LoginRequiredMixin, generic.edit.DeleteView):
    model = Certificate

    def get_success_url(self):
        return reverse('building_detail', args=[self.object.building.pk])


class CertificateListView(LoginRequiredMixin, generic.ListView):
    model = Certificate
    context_object_name = 'certificate_list'
    template_name = 'certificate/index.html'
    paginate_by = 10

    def get_ordering(self):
        ordering = self.request.GET.get('ordering', '-date_of_issue')
        return ordering


@login_required()
def certificate_create_view(request, pk_building):
    building = get_object_or_404(Building, id=pk_building)
    form = CertificateForm(request.POST or None)

    if form.is_valid():
        certificate = form.save(commit=False)
        generate_certificate(building, certificate)
        certificate.creator = request.user
        # return render(request, 'certificate/certificate.html')
        certificate.save()

        certificate.certificate_number = f"EPC-SA {str(certificate.pk).zfill(4)}-{date.today().year}"
        certificate.save()
        return redirect('building_detail', pk=building.pk)

    context = {
        'form': form,
        'building': building,
    }
    return render(request, 'certificate/create.html', context)


def generate_certificate(building: Building, certificate: Certificate):
    area = certificate.get_last_area()
    energy_sources = certificate.get_all_energy()

    df_energy = pd.DataFrame.from_dict(energy_sources)

    max_energy_reference = get_max_energy_reference(building.occupancy_class, building.climatic_zone)

    if df_energy.empty:
        energy_performance = 0
    else:
        energy_performance = df_energy['consumption__sum'].sum() / area.nett_floor_area

    energy_performance_scale = get_energy_performance_scale(energy_performance, max_energy_reference)

    energy_reference_variance = abs(max_energy_reference - energy_performance)
    energy_reference_multiple = abs(energy_performance / max_energy_reference)

    certificate.energy_benchmark = max_energy_reference
    certificate.energy_performance = energy_performance
    certificate.energy_variance = energy_reference_variance
    certificate.energy_multiple_of_reference_value = energy_reference_multiple
    certificate.energy_performance_scale = energy_performance_scale


def get_max_energy_reference(occupancy_class: str, climatic_zone: str) -> int:
    zone = int(climatic_zone) - 1
    maximum_energy_consumption_per_zone = {
        'A1': [420, 400, 440, 390, 400, 420],
        'A2': [420, 400, 440, 390, 400, 420],
        'A3': [420, 400, 440, 390, 400, 420],
        'A4': [120, 115, 125, 110, 115, 120],
        'F1': [240, 245, 260, 240, 260, 255],
        'G1': [200, 190, 210, 185, 190, 200],
        'H1': [650, 600, 585, 600, 620, 630],
    }
    max_energy_cons = maximum_energy_consumption_per_zone[occupancy_class][zone]
    return max_energy_cons


def get_energy_performance_scale(energy_performance: float, reference: float) -> str:
    if energy_performance < 0.3 * reference:
        return 'A'
    if 0.3 * reference <= energy_performance < 0.6 * reference:
        return 'B'
    if 0.6 * reference <= energy_performance < 0.9 * reference:
        return 'C'
    if 0.9 * reference <= energy_performance < 1.1 * reference:
        return 'D'
    if 1.1 * reference <= energy_performance < 1.4 * reference:
        return 'E'
    if 1.4 * reference <= energy_performance < 1.7 * reference:
        return 'F'
    if energy_performance >= 1.7 * reference:
        return 'G'


def faq_view(request):
    return render(request, 'pages/faq.html')


def training_view(request):
    return render(request, 'pages/training.html')


def generate_certificate_pdf(request, *args, **kwargs):
    """Generate a pdf document for the required certificate"""
    pk = kwargs.get('pk')
    certificate = get_object_or_404(Certificate, pk=pk)
    html_string = render_to_string("certificate/certificate.html", {"certificate": certificate})

    font_config = FontConfiguration()
    rendered_html = HTML(string=html_string, base_url=request.build_absolute_uri())
    css = [
        CSS(filename=settings.STATIC_COMPANIES / "assets" / "css" / "certificate_style.css", font_config=font_config),
        CSS(filename=settings.STATIC_CORE / "assets" / "css" / "volt.css", font_config=font_config),
    ]

    pdf_result = BytesIO()
    rendered_html.write_pdf(target=pdf_result, stylesheets=css, font_config=font_config)

    return HttpResponse(pdf_result.getvalue(), content_type="application/pdf")


def download_certificate_csv(request, *args, **kwargs):
    pk = kwargs.get('pk')
    certificate = get_object_or_404(Certificate, pk=pk)
    field_names = [field.name for field in certificate._meta.fields]

    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="export.csv"'

    writer = csv.writer(response)
    writer.writerow(field_names)
    values = []
    for field in field_names:
        value = getattr(certificate, field)
        values.append(value)
    writer.writerow(values)

    return response
