from django.db import models
from django.contrib.auth.models import User
from phonenumber_field.modelfields import PhoneNumberField
from django.utils import timezone
from django_enum import EnumField
from django.utils.text import slugify
from django.db.models import Sum
import datetime
from dateutil.relativedelta import relativedelta

def user_str(self):
    if self.first_name and self.last_name:
        return f"{self.first_name} {self.last_name}"
    if self.username:
        return self.username
    if self.email:
        return self.email
    return str(self.id)

User.__str__ = user_str

# Abstract Model Classes
class TimeMixin(models.Model):

    created = models.DateTimeField(default=timezone.now)
    last_modified = models.DateTimeField(default=timezone.now)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')
    last_modified_BY = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')

    class Meta:
        abstract = True

class IdMixin(models.Model):
    slug = models.SlugField(blank=True, unique=True, null=True)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(datetime.datetime.now().time())
        super(IdMixin, self).save(*args, **kwargs)

# Concrete Classes
class Contract(IdMixin, TimeMixin):
    class ContractTypeEnum(models.TextChoices):
        FIRM_FIX_PRICED = "FFP", "FIRM_FIX_PRICED"
        COST_PLUS_FIXED_FEE = "CPFF", "COST_PLUS_FIXED_FEE"
        MAX_NUM_CONRACT_TYPES = "MAX", "MAX_NUM_CONTRACT_TYPES"

    contract_type = EnumField(ContractTypeEnum)

    name = models.CharField(max_length=100)
    proposed_amount = models.FloatField(default=0.0)
    baseline_amount = models.FloatField(default=0.0)
    funded_amount = models.FloatField(default=0.0)
    award_amount = models.FloatField(default=0.0)
    budget_hours = models.FloatField(default=0.0)

    baseline_start = models.DateField(null=True, blank=True, default=None)
    baseline_end = models.DateField(null=True, blank=True, default=None)
    linkedContracts = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)

    def __str__(self):
        return f"{self.name} - {self.contract_type}"

    @property
    def ticket_percent_complete(self):
        charge_numbers = self.chargenumber_set.all()
        total_hours = sum(cn.budget_hours for cn in charge_numbers)
        if total_hours == 0:
            return 0.0
        
        weighted_sum = sum(cn.budget_hours * cn.get_percent_complete for cn in charge_numbers)
        return weighted_sum / total_hours

    def get_evm_data(self):
        """Compute all Earned Value Management metrics for this contract."""
        today = timezone.now().date()

        # --- Budget at Completion (BAC) ---
        bac_hours = self.budget_hours or 0.0
        bac_dollars = self.baseline_amount if self.baseline_amount > 0 else (self.funded_amount or 0.0)

        # --- Time fraction elapsed ---
        if self.baseline_start and self.baseline_end and self.baseline_end > self.baseline_start:
            total_days = (self.baseline_end - self.baseline_start).days
            elapsed_days = max(0, (min(today, self.baseline_end) - self.baseline_start).days)
            time_fraction = min(1.0, elapsed_days / total_days) if total_days > 0 else 0.0
        else:
            total_days = 0
            elapsed_days = 0
            time_fraction = 0.0

        # --- Planned Value (PV) ---
        pv_hours = bac_hours * time_fraction
        pv_dollars = bac_dollars * time_fraction

        # --- Earned Value (EV) ---
        percent_complete = self.ticket_percent_complete  # 0-100
        ev_hours = bac_hours * (percent_complete / 100.0)
        ev_dollars = bac_dollars * (percent_complete / 100.0)

        # --- Actual Cost (AC) ---
        from financial.models import TimeCardCell  # avoid circular at module level
        cells = TimeCardCell.objects.filter(
            charge_number__contract=self
        ).select_related('timeCard__employee')
        ac_hours = cells.aggregate(total=Sum('hour'))['total'] or 0.0
        ac_dollars = sum(
            (cell.hour or 0.0) * (cell.timeCard.employee.hourly_salary if cell.timeCard and cell.timeCard.employee else 0.0)
            for cell in cells
        )

        # --- Variances ---
        sv_hours = ev_hours - pv_hours
        sv_dollars = ev_dollars - pv_dollars
        cv_hours = ev_hours - ac_hours
        cv_dollars = ev_dollars - ac_dollars

        # --- Performance Indices ---
        spi = (ev_dollars / pv_dollars) if pv_dollars > 0 else 0.0
        cpi = (ev_dollars / ac_dollars) if ac_dollars > 0 else 0.0
        spi_hours = (ev_hours / pv_hours) if pv_hours > 0 else 0.0
        cpi_hours = (ev_hours / ac_hours) if ac_hours > 0 else 0.0

        # --- Monthly Time-Series for S-Curve ---
        time_series = []
        if self.baseline_start and self.baseline_end and total_days > 0:
            # Build cumulative AC by month
            ac_by_month = {}
            for cell in cells:
                if cell.date:
                    key = cell.date.strftime('%Y-%m')
                    cost = (cell.hour or 0.0) * (
                        cell.timeCard.employee.hourly_salary if cell.timeCard and cell.timeCard.employee else 0.0
                    )
                    ac_by_month[key] = ac_by_month.get(key, 0.0) + cost

            cursor = self.baseline_start.replace(day=1)
            end_limit = max(self.baseline_end, today)
            cumulative_ac = 0.0

            while cursor <= end_limit:
                month_key = cursor.strftime('%Y-%m')
                # PV at this point in time
                days_into = max(0, (cursor - self.baseline_start).days)
                pv_at = bac_dollars * min(1.0, days_into / total_days)
                # Cumulative AC
                cumulative_ac += ac_by_month.get(month_key, 0.0)
                # EV: proportional to current % complete, scaled by time
                if cursor <= today:
                    ev_at = ev_dollars * min(1.0, days_into / max(1, (today - self.baseline_start).days)) if (today - self.baseline_start).days > 0 else 0.0
                else:
                    ev_at = ev_dollars  # flat after today

                time_series.append({
                    'month': month_key,
                    'pv': round(pv_at, 2),
                    'ev': round(ev_at, 2),
                    'ac': round(cumulative_ac, 2),
                })
                cursor += relativedelta(months=1)

        return {
            'bac_hours': round(bac_hours, 2),
            'bac_dollars': round(bac_dollars, 2),
            'pv_hours': round(pv_hours, 2),
            'pv_dollars': round(pv_dollars, 2),
            'ev_hours': round(ev_hours, 2),
            'ev_dollars': round(ev_dollars, 2),
            'ac_hours': round(ac_hours, 2),
            'ac_dollars': round(ac_dollars, 2),
            'sv_hours': round(sv_hours, 2),
            'sv_dollars': round(sv_dollars, 2),
            'cv_hours': round(cv_hours, 2),
            'cv_dollars': round(cv_dollars, 2),
            'spi': round(spi, 2),
            'cpi': round(cpi, 2),
            'spi_hours': round(spi_hours, 2),
            'cpi_hours': round(cpi_hours, 2),
            'percent_complete': round(percent_complete, 2),
            'time_series': time_series,
        }

class ChargeNumber(IdMixin, TimeMixin):
    class ChargeNumberTypeEnum(models.TextChoices):
        LEVEL_OF_EFFORT = "LOE", "LEVEL_OF_EFFORT"
        QBD = "QBD", "QBD"
        ZERO_ONE_HUNDRED = "0_100", "ZERO_ONE_HUNDRED"
        FIFTY_FIFTY = "50_50", "FIFTY_FIFTY"
        MAX_NUM_TASK_TYPES = "MAX", "MAX_NUM_TASK_TYPES"

    charge_number_type = EnumField(ChargeNumberTypeEnum)
    name = models.CharField(max_length=100, blank=True, null=True)

    contract = models.ForeignKey(Contract, on_delete=models.CASCADE)
    amount = models.FloatField(default=0.0)
    budget_hours = models.FloatField(default=0.0)
    percent_complete = models.FloatField(default=0.0)
    # TODO: add validator to make sure the range is 0.0 - 100
    start_date = models.DateField(null=True, blank=True, default = None)
    end_date = models.DateField(null=True, blank=True, default = None)

    def __str__(self):
        return self.name or self.slug or f"CN-{self.id}"

    @property
    def get_percent_complete(self):
        if self.charge_number_type == self.ChargeNumberTypeEnum.QBD:
            total = self.tickets.count()
            if total == 0:
                return 0.0
            done = self.tickets.filter(status='DONE').count()
            return (done / total) * 100.0
        return self.percent_complete

    def clean(self):
        from django.core.exceptions import ValidationError
        super().clean()
        
        if self.charge_number_type == "0_100":
            if self.percent_complete not in [0.0, 100.0]:
                raise ValidationError({"percent_complete": "For ZERO_ONE_HUNDRED, percent complete must be 0 or 100."})
            if self.start_date and self.end_date:
                delta = self.end_date - self.start_date
                if delta.days > 31:
                    raise ValidationError({"end_date": "Duration cannot be more than a month for ZERO_ONE_HUNDRED."})

        if self.charge_number_type == "50_50":
            if self.percent_complete not in [0.0, 50.0, 100.0]:
                raise ValidationError({"percent_complete": "For FIFTY_FIFTY, percent complete must be 0, 50, or 100."})
            if self.start_date and self.end_date:
                delta = self.end_date - self.start_date
                if delta.days > 62:
                    raise ValidationError({"end_date": "Duration cannot be more than two months for FIFTY_FIFTY."})

class AddressModel(models.Model):
    address_1 = models.CharField(max_length=128)
    address_2 = models.CharField(max_length=128, blank=True)

    city = models.CharField(max_length=64)
    state = models.CharField(max_length=2)
    zip_code = models.CharField(max_length=5)

class Employee(IdMixin, TimeMixin):
    manager = models.ForeignKey("self", on_delete=models.CASCADE, related_name="manager_employee", null=True, blank=True)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    primaryAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="primary_address_employee", null=True, blank=True)
    workAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="work_address_employee", null=True, blank=True)
    phoneNumber = PhoneNumberField(null=True, blank=True, unique=True)
    slary= models.FloatField(default=0.0)

    def __str__(self):
        return str(self.user)

    @property
    def hourly_salary(self):
        return (self.slary or 0.0) / 2040.0
    # TODO: roles, jpbTitles



class TimeCard(IdMixin, TimeMixin):
    startDate = models.DateField(null=True, default = None)
    endDate = models.DateField(null=True, default = None)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="employee")
    approvedBy = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, related_name="approved_by")
    approvedDate = models.DateField(null=True, default=None)
    approvalComment = models.TextField(max_length=256, default="")
    # TODO: do the is approved comment

class TimeCardCell(IdMixin, TimeMixin):
    timeCard = models.ForeignKey(TimeCard, on_delete=models.CASCADE)
    date = models.DateField(null=True, default = None)
    start_time = models.TimeField(null=True, blank=True)
    end_time = models.TimeField(null=True, blank=True)
    hour = models.FloatField(default = 0.0)
    charge_number = models.ForeignKey(ChargeNumber, on_delete=models.CASCADE, null=True, blank=True)





