from __future__ import annotations

from channels.db import database_sync_to_async

from .base import *
from .meter_value import MeterValue

class Transaction(Entity):
    """Charging session data stored for each charger."""

    charger = models.ForeignKey(
        "Charger", on_delete=models.CASCADE, related_name="transactions", null=True
    )
    account = models.ForeignKey(
        CustomerAccount, on_delete=models.PROTECT, related_name="transactions", null=True
    )
    rfid = models.CharField(
        max_length=20,
        blank=True,
        verbose_name=_("RFID"),
    )
    vid = models.CharField(
        max_length=64,
        blank=True,
        default="",
        verbose_name=_("VID"),
        help_text=_("Vehicle identifier reported by the charger."),
    )
    vin = models.CharField(
        max_length=17,
        blank=True,
        help_text=_("Deprecated. Use VID instead."),
    )
    ocpp_transaction_id = models.CharField(
        max_length=128,
        blank=True,
        default="",
        help_text=_(
            "Transaction identifier reported by the charge point or generated by OCPP."
        ),
        verbose_name=_("OCPP Transaction ID"),
    )
    connector_id = models.PositiveIntegerField(null=True, blank=True)
    meter_start = models.IntegerField(null=True, blank=True)
    meter_stop = models.IntegerField(null=True, blank=True)
    voltage_start = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    voltage_stop = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    current_import_start = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    current_import_stop = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    current_offered_start = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    current_offered_stop = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    temperature_start = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    temperature_stop = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    soc_start = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    soc_stop = models.DecimalField(
        max_digits=12, decimal_places=3, null=True, blank=True
    )
    start_time = models.DateTimeField()
    stop_time = models.DateTimeField(null=True, blank=True)
    received_start_time = models.DateTimeField(null=True, blank=True)
    received_stop_time = models.DateTimeField(null=True, blank=True)

    def __str__(self) -> str:  # pragma: no cover - simple representation
        return f"{self.charger}:{self.pk}"

    class Meta:
        verbose_name = _("Transaction")
        verbose_name_plural = _("CP Transactions")

    @classmethod
    async def aget_by_ocpp_id(
        cls, charger: Charger, ocpp_transaction_id: str
    ) -> Transaction | None:
        """Return a transaction for the provided OCPP id when stored."""

        if not ocpp_transaction_id:
            return None
        return await database_sync_to_async(
            cls.objects.filter(
                charger=charger, ocpp_transaction_id=ocpp_transaction_id
            ).first
        )()

    @property
    def vehicle_identifier(self) -> str:
        """Return the preferred vehicle identifier for this transaction."""

        vid = (self.vid or "").strip()
        if vid:
            return vid

        return (self.vin or "").strip()

    @property
    def vehicle_identifier_source(self) -> str:
        """Return which field supplies :pyattr:`vehicle_identifier`."""

        if (self.vid or "").strip():
            return "vid"
        if (self.vin or "").strip():
            return "vin"
        return ""

    @property
    def kw(self) -> float:
        """Return consumed energy in kW for this session."""
        start_val = None
        if self.meter_start is not None:
            start_val = float(self.meter_start) / 1000.0

        end_val = None
        if self.meter_stop is not None:
            end_val = float(self.meter_stop) / 1000.0

        def _coerce(value):
            if value in {None, ""}:
                return None
            try:
                return float(value)
            except (TypeError, ValueError, InvalidOperation):
                return None

        if start_val is None:
            annotated_start = getattr(self, "meter_energy_start", None)
            if annotated_start is None:
                annotated_start = getattr(self, "report_meter_energy_start", None)
            start_val = _coerce(annotated_start)

        if end_val is None:
            annotated_end = getattr(self, "meter_energy_end", None)
            if annotated_end is None:
                annotated_end = getattr(self, "report_meter_energy_end", None)
            end_val = _coerce(annotated_end)

        readings: list[MeterValue] | None = None
        if start_val is None or end_val is None:
            if hasattr(self, "prefetched_meter_values"):
                readings = [
                    reading
                    for reading in getattr(self, "prefetched_meter_values")
                    if getattr(reading, "energy", None) is not None
                ]
            else:
                cache = getattr(self, "_prefetched_objects_cache", None)
                if cache and "meter_values" in cache:
                    readings = [
                        reading
                        for reading in cache["meter_values"]
                        if getattr(reading, "energy", None) is not None
                    ]

            if readings is not None:
                readings.sort(key=lambda reading: reading.timestamp)

        if readings is not None and readings:
            if start_val is None:
                start_val = _coerce(readings[0].energy)
            if end_val is None:
                end_val = _coerce(readings[-1].energy)
        elif start_val is None or end_val is None:
            readings_qs = self.meter_values.filter(energy__isnull=False).order_by(
                "timestamp"
            )
            if start_val is None:
                first_energy = readings_qs.values_list("energy", flat=True).first()
                start_val = _coerce(first_energy)
            if end_val is None:
                last_energy = readings_qs.order_by("-timestamp").values_list(
                    "energy", flat=True
                ).first()
                end_val = _coerce(last_energy)

        if start_val is None or end_val is None:
            return 0.0

        total = end_val - start_val
        return max(total, 0.0)

def annotate_transaction_energy_bounds(
    queryset, *, start_field: str = "meter_energy_start", end_field: str = "meter_energy_end"
):
    """Annotate transactions with their earliest and latest energy readings."""

    energy_qs = MeterValue.objects.filter(
        transaction=OuterRef("pk"), energy__isnull=False
    )
    start_subquery = energy_qs.order_by("timestamp").values("energy")[:1]
    end_subquery = energy_qs.order_by("-timestamp").values("energy")[:1]

    annotations = {
        start_field: Subquery(
            start_subquery, output_field=DecimalField(max_digits=12, decimal_places=3)
        ),
        end_field: Subquery(
            end_subquery, output_field=DecimalField(max_digits=12, decimal_places=3)
        ),
    }
    return queryset.annotate(**annotations)
