"""Equipments Main Routes - GMAO Collège"""
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
from flask_login import login_required, current_user
from datetime import datetime, timezone
from types import SimpleNamespace
from app_new.extensions import db
from ..core.models.equipment import Equipment, EquipmentCategory, EquipmentDocument, EquipmentRoomHistory, EquipmentRestriction, EquipmentQuantityMovement
from ..core.models.college import Room, Building, RoomType, Zone
from ..core.models.maintenance import Lot, Intervention
from ..core.models.planning import Meter, MeterReading, Consumable, EquipmentConsumable, ScheduledTask
from app_new.constants import EQUIPMENT_STATUSES
from app_new.utils import format_status, status_badge_class
from sqlalchemy.orm import selectinload, joinedload
from ..core.services.equipment_lifecycle import (
    cancel_disposal, complete_repair as complete_equipment_repair,
    confirm_disposal, propose_disposal, record_event, start_repair as start_equipment_repair,
    update_future_task_location,
)
from ..core.services.equipment_creation import create_equipment
from ..core.authorization import permission_required

def _empty_to_none(v):
    """Convertit une chaîne vide en None."""
    return None if v == '' else v


def _date_or_none(value):
    return datetime.strptime(value, '%Y-%m-%d').date() if value else None

main_bp = Blueprint("equipments", __name__, template_folder='templates')

"""
Equipments Routes - GMAO Collège
Gestion des équipements
"""
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
from flask_login import login_required, current_user
from datetime import datetime
from app_new.extensions import db
from ..core.models.equipment import Equipment, EquipmentCategory, EquipmentDocument, EquipmentRoomHistory, EquipmentRestriction, EquipmentQuantityMovement
from ..core.models.college import Room, Building, RoomType, Zone
from ..core.models.maintenance import Lot, Intervention
from ..core.models.planning import Meter, MeterReading, Consumable, EquipmentConsumable
from app_new.constants import EQUIPMENT_STATUSES


def _empty_to_none(v):
    """Convertit une chaîne vide en None."""
    return None if v == '' else v


equipments_bp = Blueprint('equipments', __name__, template_folder='templates')


@main_bp.route('/')
@login_required
def index():
    """Liste des équipements - hiérarchie parent > zone > salle > individuel."""
    search = request.args.get('search', '')
    status = request.args.get('status', '')
    category_id = request.args.get('category', type=int)
    building_id = request.args.get('building', type=int)
    view = request.args.get('view', 'families')

    if view == 'zones':
        # Vue inventaire : une ligne par type et par salle, avec quantités
        # agrégées. Les groupes parents sans salle sont ignorés afin de ne
        # pas compter deux fois leurs groupes localisés.
        grouped = {}
        rows = (Equipment.query.options(
            joinedload(Equipment.room).joinedload(Room.zone),
            joinedload(Equipment.room).joinedload(Room.building),
            joinedload(Equipment.category),
            joinedload(Equipment.parent).joinedload(Equipment.category),
            joinedload(Equipment.parent).joinedload(Equipment.room),
        ).filter_by(is_deleted=False).all())
        for eq in rows:
            room = eq.effective_room
            if not room:
                continue
            if eq.is_group:
                children = list(eq.children)
                # Un groupe structurel situé au-dessus de groupes par salle ne
                # représente pas une quantité supplémentaire.
                if any(child.is_group and not child.is_deleted for child in children):
                    continue
                display_quantity = eq.unindividualized_quantity
                if display_quantity < 1:
                    continue
            else:
                display_quantity = eq.quantity or 1
            if search and search.lower() not in (eq.name or '').lower():
                continue
            if category_id and eq.effective_category_id != category_id:
                continue
            if building_id and room.building_id != building_id:
                continue
            if status and eq.status != status:
                continue
            building = room.building
            zone = room.zone
            b = grouped.setdefault(building.id, {'name': building.name, 'zones': {}})
            z = b['zones'].setdefault(zone.id if zone else 0, {'name': zone.name if zone else 'Sans zone', 'rooms': {}})
            r = z['rooms'].setdefault(room.id, {'name': room.name, 'types': {}})
            # La vue doit distinguer les types réellement inventoriés
            # (Chaises, Prises, Portes…), et non seulement les grandes
            # catégories métier (Mobilier, Courants forts…).
            type_name = (eq.name or '').split(' #')[0].strip() or 'Sans nom'
            key = type_name.casefold()
            effective_category = eq.effective_category
            display_name = type_name
            item = r['types'].setdefault(key, {'name': display_name, 'category': effective_category.name if effective_category else '—', 'quantity': 0, 'available': 0, 'repair': 0, 'disposal': 0, 'ids': []})
            item['quantity'] += display_quantity
            if eq.lifecycle_status == 'en_reparation':
                item['repair'] += display_quantity
            elif eq.lifecycle_status in ('mise_au_rebut_planifiee', 'jete'):
                item['disposal'] += display_quantity
            else:
                item['available'] += display_quantity
            item['ids'].append(eq.id)
        zone_tree = [{'id': bid, 'name': b['name'], 'zones': [{'id': zid, 'name': z['name'], 'rooms': [{'id': rid, 'name': r['name'], 'types': list(r['types'].values())} for rid, r in z['rooms'].items()]} for zid, z in b['zones'].items()]} for bid, b in grouped.items()]
        categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
        buildings = Building.query.order_by(Building.name).all()
        return render_template('equipments/index.html', zone_tree=zone_tree, view=view, categories=categories, buildings=buildings, statuses=EQUIPMENT_STATUSES, filters={'search': search, 'status': status, 'category': category_id, 'building': building_id})

    # Pagination (page courante, 20 parents par page)
    page = request.args.get('page', 1, type=int)
    per_page = 20
    
    # Parents (équipements racine sans parent)
    parent_query = Equipment.query.filter_by(parent_id=None)
    
    if search:
        parent_query = parent_query.filter(
            db.or_(
                Equipment.name.ilike(f'%{search}%'),
                Equipment.code.ilike(f'%{search}%')
            )
        )
    
    if status:
        parent_query = parent_query.filter_by(status=status)
    
    if category_id:
        parent_query = parent_query.filter_by(category_id=category_id)
    
    if building_id:
        parent_query = parent_query.join(Room, Equipment.room_id == Room.id).filter(Room.building_id == building_id)
    
    parents_pagination = parent_query.order_by(Equipment.name).paginate(
        page=page, per_page=per_page, error_out=False
    )
    parents = parents_pagination.items
    # Les descendants sont volontairement chargés par l'endpoint hierarchy
    # uniquement lorsque l'utilisateur déplie une famille.
    for parent in parents:
        parent.zones_list = []
        def quantity_total(node, seen=None):
            seen = seen or set()
            if node.id in seen:
                return 0
            seen.add(node.id)
            children = list(node.children)
            if not children:
                return node.quantity or 1
            return sum(quantity_total(child, seen) for child in children if not child.is_deleted)
        parent.individual_count = quantity_total(parent)
    
    categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
    buildings = Building.query.order_by(Building.name).all()
    
    return render_template('equipments/index.html',
                         parents=parents,
                         pagination=parents_pagination,
                         categories=categories,
                         buildings=buildings,
                         statuses=EQUIPMENT_STATUSES,
                         filters={'search': search, 'status': status, 'category': category_id, 'building': building_id}, view=view)


@main_bp.route('/<int:id>/hierarchy')
@login_required
def hierarchy(id):
    """Charge à la demande la hiérarchie localisée d'une famille."""
    from sqlalchemy import select
    root = Equipment.query.get_or_404(id)
    descendants = select(
        Equipment.id, Equipment.parent_id, Equipment.name, Equipment.code, Equipment.is_group,
        Equipment.status, Equipment.room_id, Equipment.quantity,
        Equipment.room_id.label('effective_room_id'),
    ).where(Equipment.parent_id == root.id, Equipment.is_deleted.is_(False)).cte(recursive=True)
    descendants = descendants.union_all(select(
        Equipment.id, Equipment.parent_id, Equipment.name, Equipment.code, Equipment.is_group,
        Equipment.status, Equipment.room_id, Equipment.quantity,
        db.func.coalesce(Equipment.room_id, descendants.c.effective_room_id).label('effective_room_id'),
    ).where(Equipment.parent_id == descendants.c.id, Equipment.is_deleted.is_(False)))
    rows = db.session.execute(
        select(descendants.c.id, descendants.c.parent_id, descendants.c.name, descendants.c.code, descendants.c.is_group,
               descendants.c.status, descendants.c.quantity, Room.id.label('room_id'), Room.name.label('room_name'),
               Zone.id.label('zone_id'), Zone.name.label('zone_name'))
        .select_from(descendants)
        .join(Room, descendants.c.effective_room_id == Room.id, isouter=True)
        .join(Zone, Room.zone_id == Zone.id, isouter=True)
        .order_by(Zone.name, Room.name, descendants.c.name)
    ).all()
    # Les équipements quantitatifs sans enfants sont eux-mêmes une famille
    # localisée. La requête des descendants ne retournait alors aucune ligne,
    # masquant leur salle dans le chargement à la demande.
    if not rows and not root.is_deleted:
        room = root.effective_room
        zone = room.zone if room else None
        rows = [SimpleNamespace(
            id=root.id,
            parent_id=root.parent_id,
            name=root.name,
            code=root.code,
            is_group=root.is_group,
            status=root.status,
            quantity=root.quantity,
            room_id=room.id if room else None,
            room_name=room.name if room else None,
            zone_id=zone.id if zone else None,
            zone_name=zone.name if zone else None,
        )]
    direct_child_quantities = {}
    for row in rows:
        direct_child_quantities[row.parent_id] = direct_child_quantities.get(row.parent_id, 0) + (row.quantity or 1)
    zones = {}
    for row in rows:
        zone_key = row.zone_id or 0
        room_key = row.room_id or 0
        zone = zones.setdefault(zone_key, {'name': row.zone_name or 'Sans zone', 'rooms': {}})
        room = zone['rooms'].setdefault(room_key, {'name': row.room_name or 'Sans salle', 'items': []})
        quantity = max((row.quantity or 1) - direct_child_quantities.get(row.id, 0), 0) if row.is_group else (row.quantity or 1)
        if quantity < 1:
            continue
        room['items'].append({
            'id': row.id, 'name': row.name, 'code': row.code,
            'status': row.status, 'quantity': quantity,
            'status_label': format_status(row.status),
            'status_class': f"bg-{status_badge_class(row.status)}",
            'url': url_for('equipments.detail', id=row.id),
        })
    payload = []
    for zone in zones.values():
        rooms = list(zone['rooms'].values())
        payload.append({'name': zone['name'], 'count': sum(item['quantity'] for room in rooms for item in room['items']), 'rooms': rooms})
    return jsonify({'root_id': root.id, 'zones': payload})


@main_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create():
    """Créer un équipement."""
    if request.method == 'GET' and request.args.get('advanced') != '1' and not request.args.get('parent_id'):
        return redirect(url_for('wizard.equipment_wizard'))
    # Récupérer le parent_id depuis query string ou POST
    preselected_parent_id = request.args.get('parent_id')
    
    if request.method == 'POST':
        parent_id = request.form.get('parent_select') or request.form.get('parent_id') or None
        category_id = request.form.get('category_id', type=int)
        lot_id = request.form.get('lot_id', type=int)
        if lot_id:
            lot = db.session.get(Lot, lot_id)
            if not category_id or not lot or lot.category_id != category_id:
                flash('Le lot sélectionné doit appartenir à la catégorie choisie.', 'danger')
                return redirect(url_for('equipments.create', advanced=1)), 400
        equipment = create_equipment(
            name=request.form.get('name'), category_id=category_id,
            room_id=request.form.get('room_id') or None, lot_id=lot_id,
            code=_empty_to_none(request.form.get('code', '').strip()),
            description=_empty_to_none(request.form.get('description', '').strip()),
            status=request.form.get('status', 'en_service'),
            is_group=request.form.get('is_group') == '1',
            quantity=int(request.form.get('quantity', 1) or 1), parent_id=parent_id,
            mobility=request.form.get('mobility', 'non_precise'),
            management_mode=request.form.get('management_mode') or None,
            serial_number=_empty_to_none(request.form.get('serial_number', '').strip()),
            manufacturer=_empty_to_none(request.form.get('manufacturer', '').strip()),
            model_reference=_empty_to_none(request.form.get('model_reference', '').strip()),
            supplier_id=request.form.get('supplier_id') or None,
            supplier_reference=_empty_to_none(request.form.get('supplier_reference', '').strip()),
            purchase_price=request.form.get('purchase_price') or None,
            purchase_date=_date_or_none(request.form.get('purchase_date')),
            recurrence_monitoring=request.form.get('recurrence_monitoring') == '1',
        )
        db.session.commit()
        
        flash('Équipement créé avec succès.', 'success')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
    rooms = Room.query.order_by(Room.name).all()
    buildings = Building.query.order_by(Building.name).all()
    parent_equipments = Equipment.query.filter(Equipment.is_group == True, Equipment.parent_id == None).order_by(Equipment.name).all()
    lots = Lot.query.order_by(Lot.name).all()
    from ..core.models.company import Company
    companies = Company.query.order_by(Company.name).all()
    
    return render_template('equipments/form.html',
                         categories=categories,
                         rooms=rooms,
                         buildings=buildings,
                         parent_equipments=parent_equipments,
                         lots=lots,
                         statuses=EQUIPMENT_STATUSES,
                         companies=companies,
                         preselected_parent_id=preselected_parent_id)


@main_bp.route('/<int:id>')
@login_required
def detail(id):
    """Détail d'un équipement."""
    equipment = Equipment.query.get_or_404(id)
    root_id = equipment.parent_id or equipment.id
    if equipment.parent and equipment.parent.parent_id:
        root_id = equipment.parent.parent_id
    quantity_movements = EquipmentQuantityMovement.query.filter_by(
        root_equipment_id=root_id
    ).order_by(EquipmentQuantityMovement.moved_at.desc()).limit(50).all()
    return render_template('equipments/detail.html', equipment=equipment, quantity_movements=quantity_movements)


@main_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
@login_required
def edit(id):
    """Modifier un équipement."""
    equipment = Equipment.query.get_or_404(id)
    
    if request.method == 'POST':
        old_lot_id = equipment.lot_id
        category_id = request.form.get('category_id', type=int)
        lot_id = request.form.get('lot_id', type=int)
        if lot_id:
            lot = db.session.get(Lot, lot_id)
            if not category_id or not lot or lot.category_id != category_id:
                flash('Le lot sélectionné doit appartenir à la catégorie choisie.', 'danger')
                return redirect(url_for('equipments.edit', id=id)), 400
        equipment.name = request.form.get('name')
        equipment.code = _empty_to_none(request.form.get('code'))
        equipment.description = _empty_to_none(request.form.get('description'))
        equipment.category_id = category_id
        equipment.lot_id = lot_id
        equipment.room_id = _empty_to_none(request.form.get('room_id'))
        equipment.status = request.form.get('status', 'en_service')
        equipment.quantity = int(request.form.get('quantity', 1)) or 1
        equipment.is_group = bool(request.form.get('is_group'))
        equipment.tracked_individually = bool(request.form.get('tracked_individually'))
        equipment.position = _empty_to_none(request.form.get('position'))
        equipment.mobility = request.form.get('mobility', 'non_precise')
        equipment.management_mode = request.form.get('management_mode') or None
        equipment.serial_number = _empty_to_none(request.form.get('serial_number', '').strip())
        equipment.manufacturer = _empty_to_none(request.form.get('manufacturer', '').strip())
        equipment.model_reference = _empty_to_none(request.form.get('model_reference', '').strip())
        equipment.supplier_id = request.form.get('supplier_id') or None
        equipment.supplier_reference = _empty_to_none(request.form.get('supplier_reference', '').strip())
        equipment.purchase_price = request.form.get('purchase_price') or None
        equipment.purchase_date = _date_or_none(request.form.get('purchase_date'))
        equipment.recurrence_monitoring = bool(request.form.get('recurrence_monitoring'))
        
        # Position X/Y
        pos_x = request.form.get('position_x')
        pos_y = request.form.get('position_y')
        equipment.position_x = float(pos_x) if pos_x else None
        equipment.position_y = float(pos_y) if pos_y else None
        
        # Dates
        install_date = request.form.get('install_date')
        warranty_end = request.form.get('warranty_end')
        equipment.install_date = datetime.strptime(install_date, '%Y-%m-%d').date() if install_date else None
        equipment.warranty_end = datetime.strptime(warranty_end, '%Y-%m-%d').date() if warranty_end else None
        
        # Parent
        parent_id = request.form.get('parent_id')
        equipment.parent_id = int(parent_id) if parent_id else None
        
        # Si le lot a changé, créer les tâches planifiées
        db.session.commit()
        
        if old_lot_id != equipment.lot_id and equipment.lot_id:
            equipment.create_scheduled_tasks_from_lot()
        
        flash('Équipement modifié avec succès.', 'success')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
    rooms = Room.query.order_by(Room.name).all()
    buildings = Building.query.order_by(Building.name).all()
    parent_equipments = Equipment.query.filter(Equipment.id != id).order_by(Equipment.name).all()
    lots = Lot.query.order_by(Lot.name).all()
    from ..core.models.company import Company
    companies = Company.query.order_by(Company.name).all()
    
    # Charger les relations
    meters = Meter.query.filter_by(equipment_id=id).all()
    equipment_consumables = EquipmentConsumable.query.filter_by(equipment_id=id).all()
    all_consumables = Consumable.query.filter_by(is_active=True).order_by(Consumable.name).all()
    room_history = EquipmentRoomHistory.query.filter_by(equipment_id=id).order_by(EquipmentRoomHistory.changed_at.desc()).all()
    
    return render_template('equipments/edit.html',
                         equipment=equipment,
                         categories=categories,
                         rooms=rooms,
                         buildings=buildings,
                         parent_equipments=parent_equipments,
                         lots=lots,
                         companies=companies,
                         meters=meters,
                         equipment_consumables=equipment_consumables,
                         all_consumables=all_consumables,
                         room_history=room_history)


@main_bp.route('/<int:id>/delete', methods=['POST'])
@login_required
def delete(id):
    """Supprimer un équipement (et ses enfants en cascade)."""
    equipment = Equipment.query.get_or_404(id)
    name = equipment.name
    
    # Collecter tous les IDs (parent + enfants récursifs)
    def collect_ids(eq):
        ids = [eq.id]
        for child in eq.children:
            ids.extend(collect_ids(child))
        return ids
    
    all_ids = collect_ids(equipment)
    
    # Supprimer les dépendances en cascade (l'ordre importe peu avec les bonnes FK)
    EquipmentRoomHistory.query.filter(EquipmentRoomHistory.equipment_id.in_(all_ids)).delete(synchronize_session=False)
    EquipmentDocument.query.filter(EquipmentDocument.equipment_id.in_(all_ids)).delete(synchronize_session=False)
    EquipmentConsumable.query.filter(EquipmentConsumable.equipment_id.in_(all_ids)).delete(synchronize_session=False)
    EquipmentRestriction.query.filter(EquipmentRestriction.equipment_id.in_(all_ids)).delete(synchronize_session=False)
    Intervention.query.filter(Intervention.equipment_id.in_(all_ids)).delete(synchronize_session=False)
    
    # Supprimer les équipements (du plus profond au parent)
    def delete_recursive(eq):
        for child in list(eq.children):
            delete_recursive(child)
        db.session.delete(eq)
    
    delete_recursive(equipment)
    db.session.commit()
    
    flash(f"Équipement '{name}' et ses sous-éléments supprimés.", 'success')
    return redirect(url_for('equipments.index'))


def _cascade_soft_delete(equipment):
    """Suppression logique en cascade (si is_active existe)."""
    for child in list(equipment.children):
        _cascade_soft_delete(child)
    if hasattr(equipment, 'is_active'):
        equipment.is_active = False

@main_bp.route('/<int:id>/soft_delete', methods=['POST'])
@login_required
def soft_delete(id):
    """Suppression logique d'un équipement et de ses enfants."""
    equipment = Equipment.query.get_or_404(id)
    name = equipment.name
    
    # Si is_active n'existe pas, supprimer physiquement
    if not hasattr(equipment, 'is_active'):
        def delete_recursive(eq):
            for child in list(eq.children):
                delete_recursive(child)
            db.session.delete(eq)
        delete_recursive(equipment)
    else:
        _cascade_soft_delete(equipment)
    
    db.session.commit()
    flash(f"Équipement '{name}' et ses sous-éléments supprimés.", 'success')
    return redirect(url_for('equipments.index'))


@main_bp.route('/categories')
@login_required
def categories():
    """Liste des catégories d'équipements."""
    categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
    return render_template('equipments/categories.html', categories=categories)


@main_bp.route('/categories/<int:id>')
@login_required
def category_detail(id):
    """Détail d'une catégorie d'équipements."""
    category = EquipmentCategory.query.get_or_404(id)
    equipments = Equipment.query.filter_by(category_id=id).order_by(Equipment.name).all()
    return render_template('equipments/category_detail.html', category=category, equipments=equipments)


@main_bp.route('/categories/<int:id>/recurrence-policy', methods=['POST'])
@login_required
def category_recurrence_policy(id):
    category = EquipmentCategory.query.get_or_404(id)
    category.recurrence_alert_enabled = request.form.get('enabled') == 'on'
    category.recurrence_threshold = max(1, request.form.get('threshold', type=int) or 3)
    category.recurrence_window_days = max(1, request.form.get('window_days', type=int) or 365)
    valid_modes = {'quantitatif', 'individuel_fixe', 'individuel_mobile', 'serialise', 'structure', 'consommable'}
    requested_mode = request.form.get('default_management_mode')
    if requested_mode in valid_modes:
        category.default_management_mode = requested_mode
    db.session.commit()
    flash('Politique de récurrence mise à jour.', 'success')
    return redirect(url_for('equipments.category_detail', id=id))


@main_bp.route('/categories/new', methods=['GET', 'POST'])
@login_required
def create_category():
    """Créer une catégorie."""
    if request.method == 'POST':
        cat = EquipmentCategory(
            name=request.form.get('name'),
            description=request.form.get('description')
        )
        db.session.add(cat)
        db.session.commit()
        flash(f"Catégorie '{cat.name}' créée.", 'success')
        return redirect(url_for('equipments.categories'))
    
    return render_template('equipments/category_form.html', title='Nouvelle catégorie')


@main_bp.route('/<int:id>/status/<status>', methods=['POST'])
@login_required
def change_status(id, status):
    """Changer le statut d'un équipement."""
    valid_statuses = ['en_service', 'hs', 'a_jeter', 'jete', 'actif']
    if status not in valid_statuses:
        flash(f'Statut invalide: {status}', 'danger')
        return redirect(url_for('equipments.detail', id=id))
    
    equipment = Equipment.query.get_or_404(id)
    equipment.status = status
    db.session.commit()
    flash('Statut modifié.', 'success')
    return redirect(url_for('equipments.detail', id=id))


@main_bp.route('/<int:id>/mark-trash', methods=['POST'])
@login_required
def mark_as_trash(id):
    """Marquer un équipement comme à jeter."""
    equipment = Equipment.query.get_or_404(id)
    quantity = request.form.get('quantity', 1, type=int) or 1
    reason = _empty_to_none(request.form.get('reason', '').strip())
    try:
        units = propose_disposal(
            equipment, quantity, reason, _date_or_none(request.form.get('planned_disposal_date')),
            current_user.id,
        )
        db.session.commit()
    except ValueError as exc:
        db.session.rollback()
        flash(str(exc), 'danger')
        return redirect(url_for('equipments.detail', id=equipment.id))
    flash(f"{len(units)} unité(s) proposée(s) pour mise au rebut.", 'warning')
    return redirect(url_for('equipments.lifecycle_dashboard', status='mise_au_rebut_planifiee'))


@main_bp.route('/<int:id>/cancel-trash', methods=['POST'])
@login_required
def cancel_trash(id):
    """Annuler le statut 'à jeter'."""
    equipment = Equipment.query.get_or_404(id)
    if equipment.status != 'a_jeter':
        flash("Cet équipement n'est pas marqué comme à jeter.", 'warning')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    cancel_disposal(equipment, request.form.get('reason'), current_user.id)
    db.session.commit()
    flash(f"Équipement '{equipment.name}' remis en service.", 'success')
    return redirect(url_for('equipments.detail', id=equipment.id))


@main_bp.route('/<int:id>/confirm-trash', methods=['POST'])
@login_required
def confirm_trashed(id):
    """Confirmer qu'un équipement a été jeté."""
    equipment = Equipment.query.get_or_404(id)
    confirm_disposal(equipment, request.form.get('reason'), current_user.id)
    db.session.commit()
    flash(f"Équipement '{equipment.name}' marqué comme jeté.", 'success')
    return redirect(url_for('equipments.trashed'))


@main_bp.route('/a-jeter')
@login_required
def to_trash():
    """Liste des équipements à jeter."""
    equipments = Equipment.query.filter_by(status='a_jeter').order_by(Equipment.name).all()
    return render_template('equipments/trash_list.html', equipments=equipments, title='Équipements à jeter')


@main_bp.route('/lifecycle')
@login_required
def lifecycle_dashboard():
    """Poste de pilotage des équipements indisponibles et sortis du patrimoine."""
    status = (request.args.get('status') or 'en_reparation').strip()
    allowed = {'all', 'en_reparation', 'mise_au_rebut_planifiee', 'jete'}
    if status not in allowed:
        status = 'en_reparation'
    query = Equipment.query.filter(Equipment.is_deleted.is_(False))
    if status != 'all':
        query = query.filter(Equipment.lifecycle_status == status)
    equipments = query.order_by(Equipment.updated_at.desc()).all()
    counts = {
        key: Equipment.query.filter_by(is_deleted=False, lifecycle_status=key).count()
        for key in ('en_reparation', 'mise_au_rebut_planifiee', 'jete')
    }
    return render_template('equipments/lifecycle.html', equipments=equipments, status=status, counts=counts)


@main_bp.route('/jetes')
@login_required
def trashed():
    """Liste des équipements jetés."""
    equipments = Equipment.query.filter_by(status='jete').order_by(Equipment.updated_at.desc()).all()
    return render_template('equipments/trash_list.html', equipments=equipments, title='Anciens équipements')


@main_bp.route('/bulk-confirm-trash', methods=['POST'])
@login_required
def bulk_confirm_trash():
    """Confirmer plusieurs équipements comme jetés."""
    ids = request.form.getlist('equipment_ids')
    for eq_id in ids:
        equipment = Equipment.query.get(eq_id)
        if equipment:
            confirm_disposal(equipment, user_id=current_user.id)
    db.session.commit()
    flash(f'{len(ids)} équipements marqués comme jetés.', 'success')
    return redirect(url_for('equipments.trashed'))


@main_bp.route('/<int:id>/add-elements', methods=['GET', 'POST'])
@login_required
def add_elements(id):
    """Ajouter des éléments à un groupe existant."""
    equipment = Equipment.query.get_or_404(id)
    
    if not equipment.is_group:
        flash("Cet équipement n'est pas un groupe.", 'warning')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    if request.method == 'POST':
        count = int(request.form.get('count', 1))
        if count < 1:
            flash('La quantite ajoutee doit etre positive.', 'danger')
            return redirect(url_for('equipments.add_elements', id=equipment.id))

        first_number = equipment.individualized_quantity + 1
        for i in range(count):
            new_element = Equipment(
                name=f"{equipment.name} #{first_number + i}",
                code=f"{equipment.code}-{first_number + i}" if equipment.code else None,
                parent_id=equipment.id,
                individual_number=first_number + i,
                status='en_service',
                room_id=equipment.room_id,
                quantity=1,
                is_group=False,
                tracked_individually=True,
            )
            db.session.add(new_element)

        # L'ajout augmente le patrimoine physique du groupe.
        equipment.quantity = (equipment.quantity or 0) + count
        
        db.session.commit()
        
        # Créer les tâches planifiées pour les nouveaux éléments
        # Récupérer les enfants récemment créés
        children = Equipment.query.filter_by(parent_id=equipment.id).order_by(Equipment.id.desc()).limit(count).all()
        for child in children:
            if child.effective_lot_id:
                child.create_scheduled_tasks_from_lot()
        
        flash(f'{count} élément(s) ajouté(s) au groupe.', 'success')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    return render_template('equipments/add_elements.html', equipment=equipment)


@main_bp.route('/<int:id>/individualize', methods=['GET', 'POST'])
@login_required
def individualize(id):
    """Extraire une unite quantitative d'un groupe vers une fiche individuelle."""
    equipment = Equipment.query.get_or_404(id)
    
    if not equipment.is_group:
        flash("Seul un groupe d'équipements peut fournir une unité à individualiser.", 'warning')
        return redirect(url_for('equipments.detail', id=equipment.id))

    if equipment.unindividualized_quantity < 1:
        flash("Toutes les unités de ce groupe sont déjà individualisées.", 'warning')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    if request.method == 'POST':
        next_number = max(
            [child.individual_number or 0 for child in equipment.children] or [0]
        ) + 1
        name = (request.form.get('name') or '').strip() or f"{equipment.name} #{next_number}"
        code = (request.form.get('code') or '').strip() or None
        position = (request.form.get('position') or '').strip() or None

        individual = Equipment(
            name=name,
            code=code,
            description=equipment.description,
            status='en_service',
            parent_id=equipment.id,
            is_group=False,
            quantity=1,
            tracked_individually=True,
            individual_number=next_number,
            position=position,
            room_id=equipment.room_id,
            # Le lot et la categorie restent herites du groupe.
            lot_id=None,
            category_id=None,
        )
        db.session.add(individual)
        db.session.commit()

        if individual.effective_lot_id:
            individual.create_scheduled_tasks_from_lot()

        flash(
            f"Unité #{next_number} individualisée. Le groupe conserve un total de "
            f"{equipment.quantity} unité(s).",
            'success',
        )
        return redirect(url_for('equipments.detail', id=individual.id))

    next_number = max(
        [child.individual_number or 0 for child in equipment.children] or [0]
    ) + 1
    return render_template(
        'equipments/individualize.html',
        equipment=equipment,
        next_number=next_number,
    )


@main_bp.route('/<int:id>/children')
@login_required
def children(id):
    """Liste tous les enfants d'un groupe."""
    equipment = Equipment.query.get_or_404(id)
    
    if not equipment.is_group:
        flash("Cet équipement n'est pas un groupe.", 'warning')
        return redirect(url_for('equipments.detail', id=equipment.id))
    
    # Récupérer tous les enfants avec pagination
    page = request.args.get('page', 1, type=int)
    children = Equipment.query.filter_by(parent_id=id)\
        .order_by(Equipment.individual_number)\
        .paginate(page=page, per_page=50)
    
    return render_template('equipments/children.html', 
                         equipment=equipment, 
                         children=children)




@main_bp.route('/<int:id>/scheduled-tasks/init', methods=['POST'])
@login_required
def init_scheduled_tasks(id):
    """Initialise les tâches planifiées depuis le lot et la catégorie hérités."""
    equipment = Equipment.query.get_or_404(id)
    
    # Créer les tâches planifiées
    created_tasks = equipment.create_scheduled_tasks_from_lot()
    
    if created_tasks:
        flash(f'{len(created_tasks)} tâche(s) planifiée(s) créée(s).', 'success')
    else:
        flash('Aucune tâche planifiée créée (déjà existantes ou pas de lot/catégorie).', 'info')
    
    return redirect(url_for('equipments.scheduled_tasks', id=equipment.id))


@main_bp.route('/<int:id>/move', methods=['GET', 'POST'])
@login_required
@permission_required('patrimoine.move')
def move_to_room(id):
    """Déplace une unité ou une quantité, sans casser les totaux des groupes."""
    equipment = Equipment.query.get_or_404(id)

    if equipment.mobility == 'fixe':
        flash("Cet équipement est fixe. Sa relocalisation nécessite une modification de sa fiche.", 'danger')
        return redirect(url_for('equipments.detail', id=id))

    if request.method == 'POST':
        target_room_id = request.form.get('room_id', type=int)
        target_room = db.session.get(Room, target_room_id) if target_room_id else None
        if target_room is None:
            flash('La salle cible est invalide.', 'danger')
            return redirect(url_for('equipments.move_to_room', id=id))
        reason = _empty_to_none((request.form.get('reason') or '').strip())

        if equipment.is_group:
            quantity = request.form.get('quantity', 1, type=int)
            if quantity < 1:
                flash('La quantité doit être positive.', 'danger')
                return redirect(url_for('equipments.move_to_room', id=id))

            root = equipment.parent if equipment.parent_id else equipment
            if equipment.parent_id:
                available = equipment.unindividualized_quantity
            else:
                allocated = sum(child.quantity or 0 for child in equipment.children if child.is_group)
                available = max((equipment.quantity or 0) - allocated, 0)
            if quantity > available:
                flash(f'Seulement {available} unité(s) non individualisée(s) sont déplaçables.', 'danger')
                return redirect(url_for('equipments.move_to_room', id=id))

            target = Equipment.query.filter_by(
                parent_id=root.id, room_id=target_room.id, is_group=True, is_deleted=False
            ).first()
            if target is None:
                target = Equipment(
                    name=equipment.name if equipment.parent_id else root.name,
                    parent_id=root.id, room_id=target_room.id, is_group=True,
                    quantity=0, status='en_service', mobility=root.mobility,
                )
                db.session.add(target)
                db.session.flush()
            if equipment.parent_id and target.id == equipment.id:
                flash('La salle cible est déjà la salle source.', 'warning')
                return redirect(url_for('equipments.move_to_room', id=id))

            source_room_id = equipment.room_id if equipment.parent_id else None
            if equipment.parent_id:
                equipment.quantity -= quantity
            target.quantity = (target.quantity or 0) + quantity
            target.create_scheduled_tasks_from_lot(commit=False)
            db.session.add(EquipmentQuantityMovement(
                root_equipment_id=root.id, source_equipment_id=equipment.id,
                target_equipment_id=target.id, source_room_id=source_room_id,
                target_room_id=target_room.id, quantity=quantity, reason=reason,
                moved_by_id=current_user.id,
            ))
            record_event(root, 'transfert_quantite', current_user.id, quantity=quantity,
                         reason=f"{reason or 'Déplacement'} → {target_room.name}")
            db.session.commit()
            flash(f'{quantity} unité(s) déplacée(s) vers {target_room.name}.', 'success')
            return redirect(url_for('equipments.detail', id=root.id))

        old_room_id = equipment.effective_room.id if equipment.effective_room else None
        if old_room_id == target_room.id:
            flash('Cet équipement est déjà dans cette salle.', 'warning')
            return redirect(url_for('equipments.move_to_room', id=id))
        equipment.room_id = target_room.id
        if equipment.parent_id and equipment.parent and equipment.parent.parent_id:
            root = equipment.parent.parent
            target_group = Equipment.query.filter_by(
                parent_id=root.id, room_id=target_room.id, is_group=True, is_deleted=False
            ).first()
            if target_group:
                equipment.parent_id = target_group.id
        db.session.add(EquipmentRoomHistory(
            equipment_id=equipment.id, old_room_id=old_room_id,
            new_room_id=target_room.id, changed_by_id=current_user.id, reason=reason,
        ))
        update_future_task_location(equipment)
        record_event(equipment, 'deplacement', current_user.id, reason=reason)
        db.session.commit()
        flash('Équipement déplacé.', 'success')
        return redirect(url_for('equipments.detail', id=equipment.id))

    rooms = Room.query.join(Building).order_by(Building.name, Room.name).all()
    return render_template(
        'equipments/move.html', equipment=equipment, rooms=rooms,
        current_room=equipment.effective_room,
        max_quantity=(equipment.unindividualized_quantity if equipment.parent_id else max(
            (equipment.quantity or 0) - sum(c.quantity or 0 for c in equipment.children if c.is_group), 0
        )) if equipment.is_group else 1,
    )


@main_bp.route('/<int:id>/repair/start', methods=['POST'])
@login_required
@permission_required('patrimoine.retire')
def start_repair(id):
    equipment = Equipment.query.get_or_404(id)
    quantity = request.form.get('quantity', 1, type=int) or 1
    reason = _empty_to_none((request.form.get('reason') or '').strip())
    try:
        units, intervention = start_equipment_repair(equipment, quantity, reason, current_user.id)
        for unit in units:
            unit.create_scheduled_tasks_from_lot(commit=False)
        db.session.commit()
    except ValueError as exc:
        db.session.rollback()
        flash(str(exc), 'danger')
        return redirect(url_for('equipments.detail', id=id))
    flash(f'{len(units)} unité(s) retirée(s) pour réparation. Intervention #{intervention.id} créée.', 'warning')
    return redirect(url_for('interventions.detail', id=intervention.id))


@main_bp.route('/<int:id>/repair/complete', methods=['POST'])
@login_required
@permission_required('patrimoine.edit')
def complete_repair(id):
    equipment = Equipment.query.get_or_404(id)
    complete_equipment_repair(equipment, request.form.get('reason'), current_user.id)
    db.session.commit()
    flash('Réparation terminée, équipement remis en service.', 'success')
    return redirect(url_for('equipments.detail', id=id))


# === Gestion des Zones ===
