"""Company SHARE -> target Customer UPDATE -> Device UPDATE -> ownership rows."""
from contextlib import contextmanager
from datetime import datetime

from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.utils import timezone

from apps.customers.detail_locks import locked_customer, require_operational
from apps.customers.models import Customer
from .locking import persisted_pk
from .models import CustomerDeviceRelationship as Relationship, Device

_UNSET = object()


@contextmanager
def _write():
    try:
        with transaction.atomic():
            yield
    except IntegrityError as error:
        raise ValidationError("Ownership conflicts with the recorded timeline. Reload and retry.") from error


def _timestamp(value):
    now = timezone.now()
    if value is None:
        return now
    if not isinstance(value, datetime) or timezone.is_naive(value) or value > now:
        raise ValidationError("Supply a timezone-aware timestamp no later than now.")
    return value


def _current(device, expected=_UNSET):
    current = Relationship.objects.select_for_update().filter(device=device, ended_at__isnull=True, relationship_type="OWNER").first()
    if expected is not _UNSET and (current is None or str(current.pk) != str(expected)):
        raise ValidationError("Recorded owner changed; reload before continuing.")
    return current


def _target(device, customer):
    require_operational(customer)
    if not device.is_active:
        raise ValidationError("An active Device is required.")
    if Relationship.objects.filter(device=device, relationship_type="OWNER").exclude(customer__company_id=customer.company_id).exists():
        raise ValidationError("Device ownership history belongs to another company.")


def _append(device, customer, timestamp, note):
    if Relationship.objects.filter(device=device, relationship_type="OWNER", ended_at__gt=timestamp).exists():
        raise ValidationError("New ownership must start at or after the latest closed period.")
    row = Relationship(device=device, customer=customer, started_at=timestamp, note=note)
    row._persist()
    return row


def _close(row, timestamp, reason):
    if row is None:
        raise ValidationError("Device has no current owner.")
    row.ended_at, row.end_reason = timestamp, reason
    row._persist()
    return row


def assign_device_owner(*, device, customer, started_at=None, note=""):
    device_id, customer_id = persisted_pk(device, Device), persisted_pk(customer, Customer)
    with _write(), locked_customer(customer_id) as target:
        current_device = Device.objects.select_for_update().get(pk=device_id)
        _target(current_device, target)
        if _current(current_device):
            raise ValidationError("Device already has an owner; use transfer.")
        return _append(current_device, target, _timestamp(started_at), note)


def end_device_ownership(*, device, reason="", ended_at=None, expected_current_relationship_id=_UNSET):
    device_id = persisted_pk(device, Device)
    with _write():
        current_device = Device.objects.select_for_update().get(pk=device_id)
        return _close(_current(current_device, expected_current_relationship_id), _timestamp(ended_at), reason)


def transfer_device_ownership(*, device, new_customer, reason="", transferred_at=None, note="", expected_current_relationship_id=_UNSET):
    device_id, customer_id = persisted_pk(device, Device), persisted_pk(new_customer, Customer)
    with _write(), locked_customer(customer_id) as target:
        current_device = Device.objects.select_for_update().get(pk=device_id)
        _target(current_device, target)
        old = _current(current_device, expected_current_relationship_id)
        if old is None:
            raise ValidationError("Device has no owner to transfer.")
        if old.customer_id == target.pk:
            raise ValidationError("Device already belongs to this customer.")
        timestamp = _timestamp(transferred_at)
        _close(old, timestamp, reason)
        return _append(current_device, target, timestamp, note)
