from datetime import datetime, timedelta
from pathlib import Path

import dateutils
from django.db import models
from django.db.models import Sum
from django.shortcuts import reverse

from src.core import storage_backends
from .utils import qr
from .utils.utils import calculate_increase, clean_number


class Company(models.Model):
    name = models.CharField(max_length=400)
    trading_as = models.CharField(max_length=400)
    registration_number = models.CharField(max_length=30, unique=True)
    vat_number = models.CharField(max_length=30, unique=True)
    sic_code = models.CharField(max_length=4)

    class Meta:
        verbose_name_plural = 'Companies'

    def get_absolute_url(self):
        """Returns the url to access a detail record for this book."""
        return reverse('company_detail', args=[str(self.pk)])

    def __str__(self):
        return self.name


class Building(models.Model):
    company = models.ForeignKey(Company, on_delete=models.PROTECT)
    register_date = models.DateField(auto_now_add=True)

    class OccupancyClass(models.TextChoices):
        a1 = 'A1', 'A1 - Entertainment and public assembly'
        a2 = 'A2', 'A2 - Theatrical and indoor sport'
        a3 = 'A3', 'A3 - Places of instruction'
        a4 = 'A4', 'A4 - Worship'
        f1 = 'F1', 'F1 - Large shop'
        g1 = 'G1', 'G1 - Offices'
        h1 = 'H1', 'H1 - Hotel'

    class ClimaticZone(models.TextChoices):
        zone_1 = '1', '1 - Cold interior'
        zone_2 = '2', '2 - Temperate interior'
        zone_3 = '3', '3 - Hot interior'
        zone_4 = '4', '4 - Temperate coastal'
        zone_5 = '5', '5 - Sub-tropical coastal'
        zone_6 = '6', '6 - Arid interior'

    name = models.CharField(max_length=400, blank=True)
    address_line_1 = models.CharField(max_length=400, blank=True)
    address_line_2 = models.CharField(max_length=400, blank=True)
    address_line_3 = models.CharField(max_length=400, blank=True)
    address_line_4 = models.CharField(max_length=400, blank=True)
    address_line_5 = models.CharField(max_length=400, blank=True)

    building_owner = models.CharField(max_length=400)
    number_floors = models.IntegerField(null=True, blank=True)

    occupancy_rate = models.DecimalField(decimal_places=1, max_digits=4)
    occupancy_class = models.CharField(max_length=2, choices=OccupancyClass.choices)
    climatic_zone = models.CharField(max_length=1, choices=ClimaticZone.choices)
    cadastral_info = models.CharField(max_length=400, null=True, blank=True)

    building_plan_approval_date = models.DateField(null=True, blank=True)
    occupancy_certificate_date = models.DateField(null=True, blank=True)
    year_construction = models.CharField(max_length=4, null=True, blank=True)
    year_last_renovation = models.CharField(max_length=4, null=True, blank=True)

    # It is recommended that the year of construction was longer than 2 years back
    # It is recommended that the last major renovation was longer than 2 years back

    unique_building_code = models.CharField(max_length=12, null=True, blank=True)

    def address(self):
        return ', '.join(
            [
                str(x)
                for x in [
                    self.address_line_1,
                    self.address_line_2,
                    self.address_line_3,
                    self.address_line_4,
                    self.address_line_5,
                ]
                if x != ''
            ]
        )

    def get_absolute_url(self):
        """Returns the url to access a detail record for this book."""
        return reverse('building_detail', args=[str(self.pk)])

    def __str__(self):
        return self.address()

    @staticmethod
    def monthly_increase() -> int:
        previous_month = (datetime.today().replace(day=1) - timedelta(days=1)).month
        previous_month_count = clean_number(Building.objects.filter(register_date__month=previous_month).count())
        current_month_count = clean_number(Building.objects.filter(register_date__month=datetime.today().month).count())
        increase = calculate_increase(current_month_count, previous_month_count)
        return increase

    def generate_building_code(self) -> str:
        building_set = Building.objects.filter(company=self.company)
        client_num = ""
        # Finds existing client (company) number
        for x in building_set:
            if x.unique_building_code is None:
                continue
            if len(x.unique_building_code) > 0:
                client_num = x.unique_building_code[:6]
                break
        # Generates a new client number and checks if it already exists
        if len(client_num) == 0:
            client_num = f"00{len(Company.objects.filter(name__startswith=self.company.name[:3]))}"
            does_exist = Building.objects.filter(
                unique_building_code__startswith=self.company.name[:3].upper() + client_num).exists()
            if does_exist:
                client_num = f"00{int(client_num[2]) + 1}"
        building_num = f"00{len(Building.objects.filter(name__startswith=self.name[:3], company=self.company)) + 1}"
        code = f"{self.company.name[:3].upper()}{client_num[-3:]}{self.name[:3].upper()}{building_num[-3:]}"
        return code


class Area(models.Model):
    building = models.ForeignKey(Building, on_delete=models.CASCADE)

    date = models.DateField()
    total_floor_area = models.DecimalField(null=True, blank=True, decimal_places=1, max_digits=20)
    excluded_floor_area = models.DecimalField(null=True, blank=True, decimal_places=1, max_digits=20)
    nett_floor_area = models.DecimalField(null=True, blank=True, decimal_places=1, max_digits=20)

    @staticmethod
    def monthly_increase() -> int:
        previous_month = (datetime.today().replace(
            day=1) - timedelta(days=1)).month
        previous_month_sum = clean_number(Area.objects.filter(
            date__month=previous_month).aggregate(Sum('nett_floor_area'))['nett_floor_area__sum'])
        current_month_sum = clean_number(Area.objects.filter(
            date__month=datetime.today().month).aggregate(Sum('nett_floor_area'))['nett_floor_area__sum'])
        increase = calculate_increase(current_month_sum, previous_month_sum)
        return increase


def get_area_filepath(instance, filename):
    return Path(f"{instance.link.building_id}") / "area" / filename


class AreaDocument(models.Model):
    link = models.ForeignKey(Area, on_delete=models.CASCADE)
    supporting_doc = models.FileField(null=True, blank=True, storage=storage_backends.PrivateMediaStorage(),
                                      upload_to=get_area_filepath)
    upload_date = models.DateTimeField(auto_now_add=True)


class Energy(models.Model):
    building = models.ForeignKey(Building, on_delete=models.CASCADE)

    class Sources(models.TextChoices):
        grid = '1', 'Electricity (grid)'
        renewable = '2', 'Electricity (renewable)'
        gas = '3', 'Gas'
        solid = '4', 'Solid fuel'
        liquid = '5', 'Liquid fuel'
        other = '6', 'Other'

    source = models.CharField(max_length=1, choices=Sources.choices)
    date = models.DateField()
    consumption = models.IntegerField()
    exclude = models.BooleanField()

    @staticmethod
    def monthly_increase(energy_type) -> int:
        previous_month = (datetime.today().replace(
            day=1) - timedelta(days=1)).month
        previous_month_sum = clean_number(Energy.objects.filter(
            date__month=previous_month, source=energy_type).aggregate(Sum('consumption'))['consumption__sum'])
        current_month_sum = clean_number(Energy.objects.filter(
            date__month=datetime.today().month, source=energy_type).aggregate(Sum('consumption'))['consumption__sum'])
        increase = calculate_increase(current_month_sum, previous_month_sum)
        return increase

    @staticmethod
    def monthly_emissions_increase() -> int:
        previous_month = (datetime.today().replace(
            day=1) - timedelta(days=1)).month
        ghg_factors = {
            'gas': (0.035417, Energy.Sources.gas),
            'solid': (0.042763, Energy.Sources.solid),
            'liquid': (0.040330, Energy.Sources.liquid),
            'grid': (0.00094, Energy.Sources.grid)
        }
        previous_month_sum, current_month_sum = 0, 0
        for value in ghg_factors.values():
            previous_month_sum += clean_number(
                Energy.objects.filter(date__month=previous_month, source=value[1]).aggregate(Sum('consumption'))[
                    'consumption__sum']) * value[0]
            current_month_sum += clean_number(
                Energy.objects.filter(date__month=datetime.today().month, source=value[1]).aggregate(
                    Sum('consumption'))[
                    'consumption__sum']) * value[0]
        increase = calculate_increase(round(current_month_sum, 3), round(previous_month_sum, 3))
        return increase


def get_energy_filepath(instance, filename):
    return Path(f"{instance.link.building_id}") / "energy" / filename


class EnergyDocument(models.Model):
    link = models.ForeignKey(Energy, on_delete=models.CASCADE)
    supporting_doc = models.FileField(null=True, blank=True, storage=storage_backends.PrivateMediaStorage(),
                                      upload_to=get_energy_filepath)
    upload_date = models.DateTimeField(auto_now_add=True)


class Assessor(models.Model):
    name = models.CharField(max_length=200)


class AccreditedBody(models.Model):
    name = models.CharField(max_length=200)
    accreditation_number = models.CharField(max_length=200)
    assessor_name = models.ForeignKey(Assessor, on_delete=models.CASCADE)


class Certificate(models.Model):
    building = models.ForeignKey(Building, on_delete=models.PROTECT)
    accredited_body = models.ForeignKey(AccreditedBody, on_delete=models.CASCADE, null=True, blank=True)

    certificate_number = models.CharField(null=True, blank=True, max_length=20)
    project_name = models.CharField(max_length=200)

    energy_benchmark = models.DecimalField(decimal_places=3, max_digits=9)
    energy_performance = models.DecimalField(decimal_places=3, max_digits=12)
    energy_variance = models.DecimalField(decimal_places=3, max_digits=9)
    energy_multiple_of_reference_value = models.DecimalField(decimal_places=3, max_digits=9)
    energy_performance_scale = models.CharField(max_length=1)

    end_date = models.DateField()
    date_of_issue = models.DateField()

    def __str__(self):
        return f'{self.building.address()} - {self.end_date}'

    def qr_code(self):
        return qr.get_certificate_qr_code(self.building.address())

    def get_start_date(self):
        return self.end_date - dateutils.relativedelta(months=12)

    def get_valid_until_date(self):
        return self.end_date + dateutils.relativedelta(years=5)

    def energy_performance_display(self):
        return round(self.energy_performance)

    def get_last_area(self) -> Area:
        return self.building.area_set.all().order_by('-date').last()

    def get_all_energy(self) -> models.QuerySet:
        choices = dict(Energy._meta.get_field('source').flatchoices)
        whens = [models.When(source=k, then=models.Value(v)) for k, v in choices.items()]
        return (
            self.building.energy_set.filter(
                building__energy__date__gte=self.get_start_date(), building__energy__date__lte=self.end_date
            )
                .values(Energy.source.field.attname, Energy.exclude.field.attname)
                .annotate(models.Sum(Energy.consumption.field.attname, distinct=True))
                .annotate(get_source_display=models.Case(*whens, output_field=models.CharField()))
                .order_by()
        )

    @staticmethod
    def monthly_increase() -> int:
        previous_month = (datetime.today().replace(day=1) - timedelta(days=1)).month
        previous_month_count = clean_number(Certificate.objects.filter(
            date_of_issue__month=previous_month).count())
        current_month_count = clean_number(Certificate.objects.filter(
            date_of_issue__month=datetime.today().month).count())
        increase = calculate_increase(current_month_count, previous_month_count)
        return increase

    @staticmethod
    def monthly_energy_increase() -> int:
        previous_month = (datetime.today().replace(
            day=1) - timedelta(days=1)).month
        previous_month_sum = clean_number(Certificate.objects.filter(
            date_of_issue__month=previous_month).aggregate(Sum('energy_performance'))['energy_performance__sum'])
        current_month_sum = clean_number(Certificate.objects.filter(
            date_of_issue__month=datetime.today().month).aggregate(Sum('energy_performance'))[
                                             'energy_performance__sum'])
        increase = calculate_increase(current_month_sum, previous_month_sum)
        return increase
