"""Règles métier du cycle de vie des équipements et de leurs échéances."""
from datetime import date, datetime, timedelta, timezone

from app_new.extensions import db
from app_new.core.models.equipment import Equipment, EquipmentLifecycleEvent
from app_new.core.models.maintenance import Intervention
from app_new.core.models.planning import ScheduledTask


OPEN_TASK_STATUSES = ("planned", "in_progress", "suspended")


def record_event(equipment, event_type, user_id=None, previous_status=None,
                 new_status=None, quantity=1, reason=None, intervention_id=None):
    """Enregistre un événement avec la localisation et le lot tels qu'ils sont aujourd'hui."""
    room = equipment.effective_room
    lot = equipment.effective_lot
    event = EquipmentLifecycleEvent(
        equipment=equipment,
        event_type=event_type,
        previous_status=previous_status,
        new_status=new_status,
        quantity=max(quantity or 1, 1),
        reason=reason,
        room_id_snapshot=room.id if room else None,
        room_name_snapshot=room.name if room else None,
        lot_id_snapshot=lot.id if lot else None,
        lot_name_snapshot=lot.name if lot else None,
        intervention_id=intervention_id,
        created_by_id=user_id,
    )
    db.session.add(event)
    return event


def extract_units(group, quantity, user_id=None, reason=None):
    """Individualise des unités d'un groupe sans modifier son total physique."""
    if not group.is_group:
        if quantity != 1:
            raise ValueError("Un équipement individuel représente une seule unité.")
        return [group]
    quantity = max(int(quantity or 0), 0)
    if quantity < 1 or quantity > group.unindividualized_quantity:
        raise ValueError(
            f"Quantité invalide : {group.unindividualized_quantity} unité(s) seulement sont disponibles."
        )
    root = group
    while root.parent_id:
        root = root.parent
    next_number = max([item.individual_number or 0 for item in root.all_children] or [0]) + 1
    created = []
    room = group.effective_room
    for offset in range(quantity):
        unit = Equipment(
            name=f"{root.name} #{next_number + offset}",
            description=root.description,
            status="en_service",
            lifecycle_status="en_service",
            parent_id=group.id,
            room_id=room.id if room else None,
            is_group=False,
            quantity=1,
            tracked_individually=True,
            individual_number=next_number + offset,
            mobility=root.mobility,
        )
        db.session.add(unit)
        db.session.flush()
        record_event(unit, "individualisation", user_id, new_status="en_service", reason=reason)
        created.append(unit)
    return created


def _open_tasks(equipment):
    return ScheduledTask.query.filter(
        ScheduledTask.equipment_id == equipment.id,
        ScheduledTask.status.in_(OPEN_TASK_STATUSES),
    ).all()


def _suspend_preventive(equipment, reason):
    now = datetime.now(timezone.utc)
    for task in _open_tasks(equipment):
        rule = task.lot_task
        if rule and (rule.is_safety_critical or rule.unavailable_policy == "keep"):
            continue
        task.status = "suspended"
        task.suspension_reason = reason
        task.suspended_at = now


def _cancel_preventive(equipment, reason, keep_safety=False):
    for task in _open_tasks(equipment):
        if keep_safety and task.lot_task and task.lot_task.is_safety_critical:
            continue
        task.status = "cancelled"
        task.postpone_reason = reason
        task.suspension_reason = None


def _resume_preventive(equipment):
    """Réactive les échéances suspendues en appliquant la règle du lot."""
    from app_new.core.services.maintenance_engine import next_working_day

    today = date.today()
    for task in _open_tasks(equipment):
        if task.status != "suspended":
            continue
        rule = task.lot_task
        if rule and rule.after_repair_policy == "reset" and rule.jours_entre_interventions:
            target = today + timedelta(days=rule.jours_entre_interventions)
        else:
            target = max(task.scheduled_date or today, today)
        task.scheduled_date = next_working_day(target)
        task.status = "planned"
        task.suspension_reason = None
        task.suspended_at = None
        task.lifecycle_status_snapshot = "en_service"


def create_corrective_intervention(source, units, reason, user_id=None):
    room = source.effective_room
    lot = source.effective_lot
    quantity = len(units)
    intervention = Intervention(
        title=f"Réparation — {quantity} × {source.name}"[:300],
        description=reason or "Équipement retiré provisoirement pour diagnostic et réparation.",
        type="curatif",
        status="en_attente",
        priority="normale",
        equipment_id=source.id,
        room_id=room.id if room else None,
        lot_id=lot.id if lot else None,
        quantity_affected=quantity,
        individual_numbers=",".join(str(unit.individual_number) for unit in units if unit.individual_number),
        # L'intervention reste non affectée jusqu'à sa prise en charge. L'auteur
        # de l'action est conservé dans le journal de cycle de vie.
    )
    db.session.add(intervention)
    db.session.flush()
    return intervention


def start_repair(source, quantity=1, reason=None, user_id=None, create_intervention=True):
    units = extract_units(source, quantity, user_id=user_id, reason=reason) if source.is_group else [source]
    intervention = create_corrective_intervention(source, units, reason, user_id) if create_intervention else None
    now = datetime.now(timezone.utc)
    for unit in units:
        previous = unit.lifecycle_status
        unit.lifecycle_status = "en_reparation"
        unit.status = "en_maintenance"
        unit.unavailable_since = now
        _suspend_preventive(unit, "Équipement indisponible pendant sa réparation")
        record_event(unit, "retrait_reparation", user_id, previous, unit.lifecycle_status,
                     reason=reason, intervention_id=intervention.id if intervention else None)
    return units, intervention


def complete_repair(equipment, reason=None, user_id=None):
    previous = equipment.lifecycle_status
    equipment.lifecycle_status = "en_service"
    equipment.status = "en_service"
    equipment.unavailable_since = None
    equipment.create_scheduled_tasks_from_lot(commit=False)
    _resume_preventive(equipment)
    record_event(equipment, "reparation_terminee", user_id, previous, "en_service", reason=reason)


def propose_disposal(source, quantity=1, reason=None, planned_date=None, user_id=None):
    units = extract_units(source, quantity, user_id=user_id, reason=reason) if source.is_group else [source]
    for unit in units:
        unit.create_scheduled_tasks_from_lot(commit=False)
        previous = unit.lifecycle_status
        unit.status = "a_jeter"
        unit.lifecycle_status = "mise_au_rebut_planifiee"
        unit.planned_disposal_date = planned_date
        unit.disposal_reason = reason
        _cancel_preventive(unit, "Mise au rebut proposée", keep_safety=True)
        record_event(unit, "rebut_propose", user_id, previous, unit.lifecycle_status, reason=reason)
    return units


def cancel_disposal(equipment, reason=None, user_id=None):
    previous = equipment.lifecycle_status
    equipment.status = "en_service"
    equipment.lifecycle_status = "en_service"
    equipment.planned_disposal_date = None
    equipment.disposal_reason = None
    _resume_preventive(equipment)
    record_event(equipment, "rebut_annule", user_id, previous, "en_service", reason=reason)


def confirm_disposal(equipment, reason=None, user_id=None):
    previous = equipment.lifecycle_status
    equipment.status = "jete"
    equipment.lifecycle_status = "jete"
    equipment.disposal_date = date.today()
    equipment.disposal_reason = reason or equipment.disposal_reason
    _cancel_preventive(equipment, "Équipement sorti définitivement du patrimoine")
    record_event(equipment, "rebut_confirme", user_id, previous, "jete", reason=equipment.disposal_reason)
    equipment.room_id = None


def update_future_task_location(equipment):
    room = equipment.effective_room
    for task in _open_tasks(equipment):
        if task.intervention_id:
            continue
        task.room_id = room.id if room else None
        task.room_name_snapshot = room.name if room else None
