"""
Modèles pour la planification et les maintenance préventive - GMAO Collège
"""
from datetime import datetime, timezone, date, time
from ...extensions import db


class WorkSchedule(db.Model):
    """Horaires de travail."""
    __tablename__ = "work_schedules"
    
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    academic_year = db.Column(db.Integer, nullable=True, index=True)
    
    day_of_week = db.Column(db.Integer)
    start_time = db.Column(db.Time)
    end_time = db.Column(db.Time)
    lunch_start = db.Column(db.Time, nullable=True)  # Début pause déjeuner
    lunch_end = db.Column(db.Time, nullable=True)  # Fin pause déjeuner
    template_id = db.Column(db.Integer, db.ForeignKey("work_schedule_templates.id"), nullable=True)
    
    is_active = db.Column(db.Boolean, default=True)
    
    user = db.relationship("User", backref=db.backref("work_schedules", lazy="dynamic"))
    template = db.relationship("WorkScheduleTemplate", back_populates="assignments")
    
    def __repr__(self):
        return f"<WorkSchedule {self.day_of_week} {self.start_time}-{self.end_time}>"


class WorkScheduleTemplate(db.Model):
    """Un des trois profils horaires paramétrables de l'agent."""
    __tablename__ = "work_schedule_templates"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
    name = db.Column(db.String(80), nullable=False)
    start_time = db.Column(db.Time, nullable=False)
    end_time = db.Column(db.Time, nullable=False)
    lunch_start = db.Column(db.Time, nullable=True)
    lunch_end = db.Column(db.Time, nullable=True)
    is_active = db.Column(db.Boolean, nullable=False, default=True)

    user = db.relationship("User", backref=db.backref("work_schedule_templates", lazy="dynamic"))
    assignments = db.relationship("WorkSchedule", back_populates="template")

    @property
    def duration_minutes(self):
        total = (self.end_time.hour * 60 + self.end_time.minute) - (self.start_time.hour * 60 + self.start_time.minute)
        if self.lunch_start and self.lunch_end:
            total -= (self.lunch_end.hour * 60 + self.lunch_end.minute) - (self.lunch_start.hour * 60 + self.lunch_start.minute)
        return max(total, 0)


class AnnualTimeConfig(db.Model):
    """Objectif annuel et déduction de pénibilité pour un agent."""
    __tablename__ = "annual_time_configs"
    __table_args__ = (db.UniqueConstraint("user_id", "year", name="uq_annual_time_user_year"),)

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
    year = db.Column(db.Integer, nullable=False)
    target_minutes = db.Column(db.Integer, nullable=False, default=1607 * 60)
    hardship_deduction_minutes = db.Column(db.Integer, nullable=False, default=0)
    notes = db.Column(db.Text, nullable=True)

    user = db.relationship("User", backref=db.backref("annual_time_configs", lazy="dynamic"))

    @property
    def minutes_to_work(self):
        return max((self.target_minutes or 0) - (self.hardship_deduction_minutes or 0), 0)


class TimeEntry(db.Model):
    """Suivi réel d'une journée : retard, présence, permanence ou heures sup."""
    __tablename__ = "work_time_entries"
    __table_args__ = (db.UniqueConstraint("user_id", "work_date", name="uq_work_time_user_date"),)

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
    work_date = db.Column(db.Date, nullable=False, index=True)
    planned_minutes = db.Column(db.Integer, nullable=False, default=0)
    actual_minutes = db.Column(db.Integer, nullable=False, default=0)
    entry_type = db.Column(db.String(30), nullable=False, default="travail")
    note = db.Column(db.Text, nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

    user = db.relationship("User", backref=db.backref("work_time_entries", lazy="dynamic"))

    @property
    def variance_minutes(self):
        return (self.actual_minutes or 0) - (self.planned_minutes or 0)


class CollegeClosure(db.Model):
    """Fermetures du collège / Vacances scolaires."""
    __tablename__ = "college_closures"
    
    id = db.Column(db.Integer, primary_key=True)
    
    name = db.Column(db.String(255), nullable=False)
    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=False)
    
    closure_type = db.Column(db.String(50), default='vacances')
    work_hours_type = db.Column(db.String(20), default='none')  # none, reduced, custom
    notes = db.Column(db.Text)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    
    # Relation avec les horaires spécifiques
    schedules = db.relationship("ClosureSchedule", backref="closure", lazy="dynamic", 
                                 cascade="all, delete-orphan")
    
    def __repr__(self):
        return f"<CollegeClosure {self.name}>"


class ClosureSchedule(db.Model):
    """Horaires de travail pendant une période de vacances."""
    __tablename__ = "closure_schedules"
    
    id = db.Column(db.Integer, primary_key=True)
    closure_id = db.Column(db.Integer, db.ForeignKey("college_closures.id"), nullable=False)
    
    day_of_week = db.Column(db.Integer, nullable=False)  # 0=Dimanche, 1=Lundi, etc.
    start_time = db.Column(db.Time)
    end_time = db.Column(db.Time)
    lunch_start = db.Column(db.Time)  # Début pause déjeuner
    lunch_end = db.Column(db.Time)    # Fin pause déjeuner
    
    is_active = db.Column(db.Boolean, default=True)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    
    def __repr__(self):
        days = {0: 'Dimanche', 1: 'Lundi', 2: 'Mardi', 3: 'Mercredi', 4: 'Jeudi', 5: 'Vendredi', 6: 'Samedi'}
        return f"<ClosureSchedule {days.get(self.day_of_week)} {self.start_time}-{self.end_time}>"


class ClosureWorkDay(db.Model):
    """Journée exceptionnellement travaillée pendant des vacances scolaires."""
    __tablename__ = "closure_work_days"
    __table_args__ = (
        db.UniqueConstraint("closure_id", "work_date", name="uq_closure_work_day"),
    )

    id = db.Column(db.Integer, primary_key=True)
    closure_id = db.Column(
        db.Integer,
        db.ForeignKey("college_closures.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    template_id = db.Column(db.Integer, db.ForeignKey("work_schedule_templates.id"), nullable=True, index=True)
    work_date = db.Column(db.Date, nullable=False, index=True)
    start_time = db.Column(db.Time, nullable=False)
    end_time = db.Column(db.Time, nullable=False)
    lunch_start = db.Column(db.Time, nullable=True)
    lunch_end = db.Column(db.Time, nullable=True)
    notes = db.Column(db.String(255), nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))

    closure = db.relationship(
        "CollegeClosure",
        backref=db.backref("work_days", cascade="all, delete-orphan", order_by="ClosureWorkDay.work_date"),
    )
    template = db.relationship("WorkScheduleTemplate")

    def __repr__(self):
        return f"<ClosureWorkDay {self.work_date} {self.start_time}-{self.end_time}>"


class PersonalLeave(db.Model):
    """Congés personnels."""
    __tablename__ = "personal_leaves"
    
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    
    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=False)
    leave_type = db.Column(db.String(50))
    
    notes = db.Column(db.Text)
    is_approved = db.Column(db.Boolean, default=False)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    
    user = db.relationship("User", backref=db.backref("personal_leaves", lazy="dynamic"))
    
    def __repr__(self):
        return f"<PersonalLeave {self.user_id} {self.start_date}-{self.end_date}>"


class Training(db.Model):
    """Formation."""
    __tablename__ = "trainings"
    __table_args__ = (db.UniqueConstraint("source_type", "source_id", name="uq_training_source"),)
    
    id = db.Column(db.Integer, primary_key=True)
    
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    
    start_date = db.Column(db.Date)
    end_date = db.Column(db.Date)
    start_time = db.Column(db.Time, nullable=True)
    end_time = db.Column(db.Time, nullable=True)
    location = db.Column(db.String(255))
    source_type = db.Column(db.String(30), nullable=True)
    source_id = db.Column(db.String(191), nullable=True)
    source_subject = db.Column(db.String(500), nullable=True)
    source_sender = db.Column(db.String(255), nullable=True)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    
    participants = db.relationship("TrainingParticipant", back_populates="training", cascade="all, delete-orphan")
    
    def __repr__(self):
        return f"<Training {self.name}>"


class TrainingParticipant(db.Model):
    """Participant à une formation."""
    __tablename__ = "training_participants"
    
    id = db.Column(db.Integer, primary_key=True)
    
    training_id = db.Column(db.Integer, db.ForeignKey("trainings.id"))
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    
    is_confirmed = db.Column(db.Boolean, default=False)
    notes = db.Column(db.Text)
    
    training = db.relationship("Training", back_populates="participants")
    user = db.relationship("User")
    
    def __repr__(self):
        return f"<TrainingParticipant {self.user_id}>"


class Meter(db.Model):
    """Compteur physique rattaché à un élément du patrimoine.

    ``equipment_id`` reste le rattachement historique, mais un compteur peut
    désormais être porté directement par un bâtiment, une zone, un local ou
    un logement.  Les rattachements sont explicites afin de conserver
    l'intégrité référentielle SQL.
    """
    __tablename__ = "meters"
    __table_args__ = (
        db.CheckConstraint(
            "(equipment_id IS NOT NULL) + (building_id IS NOT NULL) + "
            "(zone_id IS NOT NULL) + (room_id IS NOT NULL) + "
            "(housing_unit_id IS NOT NULL) = 1",
            name="ck_meters_one_scope",
        ),
    )
    
    id = db.Column(db.Integer, primary_key=True)
    equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"), nullable=True, index=True)
    building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=True, index=True)
    zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"), nullable=True, index=True)
    room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"), nullable=True, index=True)
    housing_unit_id = db.Column(db.Integer, db.ForeignKey("housing_units.id"), nullable=True, index=True)

    parent_id = db.Column(db.Integer, db.ForeignKey("meters.id"), nullable=True, index=True)
    replaces_meter_id = db.Column(db.Integer, db.ForeignKey("meters.id"), nullable=True, index=True)
    
    name = db.Column(db.String(100), nullable=False)
    meter_type = db.Column(db.String(50), default="hours")
    unit = db.Column(db.String(20), default="h")
    usage = db.Column(db.String(255), nullable=True)
    # Rôle explicite uniquement pour les calculs de contrats photocopieurs.
    # NONE conserve les compteurs historiques/non contractuels sans les
    # classer silencieusement en N&B.
    contract_role = db.Column(db.String(20), nullable=False, default="NONE", server_default="NONE")
    status = db.Column(db.String(30), nullable=False, default="active", server_default="active")
    remainder_label = db.Column(db.String(255), nullable=True)
    
    current_value = db.Column(db.Float, default=0)
    initial_value = db.Column(db.Float, default=0)
    
    warning_threshold = db.Column(db.Float)
    critical_threshold = db.Column(db.Float)
    
    maintenance_interval = db.Column(db.Float)
    last_maintenance_value = db.Column(db.Float)
    
    # Intervalle de relevé en jours (pour planification)
    reading_interval_days = db.Column(db.Integer)
    last_reading_date = db.Column(db.Date)
    
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    equipment = db.relationship("Equipment", backref=db.backref("meters", lazy="dynamic"))
    building = db.relationship("Building", backref=db.backref("meters", lazy="dynamic"))
    zone = db.relationship("Zone", backref=db.backref("meters", lazy="dynamic"))
    room = db.relationship("Room", backref=db.backref("meters", lazy="dynamic"))
    housing_unit = db.relationship("HousingUnit", backref=db.backref("meters", lazy="dynamic"))
    parent = db.relationship("Meter", remote_side=[id], foreign_keys=[parent_id], backref=db.backref("children", lazy="dynamic"))
    replaced_meter = db.relationship("Meter", remote_side=[id], foreign_keys=[replaces_meter_id], backref=db.backref("replacement", uselist=False))
    readings = db.relationship("MeterReading", back_populates="meter", lazy="dynamic")
    
    @property
    def next_maintenance_at(self):
        if self.last_maintenance_value and self.maintenance_interval:
            return self.last_maintenance_value + self.maintenance_interval
        return None
    
    @property
    def needs_maintenance(self):
        if self.next_maintenance_at and self.current_value >= self.next_maintenance_at:
            return True
        if self.critical_threshold and self.current_value >= self.critical_threshold:
            return True
        return False
    
    @property
    def warning_level(self):
        if self.critical_threshold and self.current_value >= self.critical_threshold:
            return "critical"
        if self.warning_threshold and self.current_value >= self.warning_threshold:
            return "warning"
        if self.needs_maintenance:
            return "maintenance"
        return "ok"
    
    def __repr__(self):
        return f"<Meter {self.name} ({self.current_value}{self.unit})>"


class MeterReading(db.Model):
    """Relevé de compteur."""
    __tablename__ = "meter_readings"
    
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id"), nullable=False)
    
    value = db.Column(db.Float, nullable=False)
    reading_date = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    read_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"))
    
    notes = db.Column(db.Text)
    is_reset = db.Column(db.Boolean, nullable=False, default=False)
    reset_reason = db.Column(db.String(255), nullable=True)
    photo_filename = db.Column(db.String(255), nullable=True)
    photo_path = db.Column(db.String(500), nullable=True)
    
    meter = db.relationship("Meter", back_populates="readings")
    read_by = db.relationship("User")
    
    def __repr__(self):
        return f"<MeterReading {self.value}>"


class MeterReadingCorrection(db.Model):
    """Audit immuable de chaque correction d'un relevé existant."""
    __tablename__ = "meter_reading_corrections"

    id = db.Column(db.Integer, primary_key=True)
    reading_id = db.Column(db.Integer, db.ForeignKey("meter_readings.id", ondelete="CASCADE"), nullable=False, index=True)
    old_value = db.Column(db.Float, nullable=False)
    new_value = db.Column(db.Float, nullable=False)
    corrected_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    corrected_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    reason = db.Column(db.Text, nullable=False)

    reading = db.relationship("MeterReading", backref=db.backref("corrections", lazy="selectin"))
    corrected_by = db.relationship("User")


class MeterReadingSchedule(db.Model):
    """Règle de planification des relevés d'un compteur."""
    __tablename__ = "meter_reading_schedules"
    __table_args__ = (
        db.UniqueConstraint("meter_id", name="uq_meter_reading_schedule_meter"),
        db.CheckConstraint(
            "frequency in ('WEEKLY', 'MONTHLY', 'SEMI_ANNUAL', 'ANNUAL', 'FIXED_ANNUAL_DATE')",
            name="ck_meter_reading_schedule_frequency",
        ),
    )

    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    is_active = db.Column(db.Boolean, nullable=False, default=True, server_default="1")
    frequency = db.Column(db.String(30), nullable=False)
    reference_date = db.Column(db.Date, nullable=False)
    target_time = db.Column(db.Time, nullable=True)
    duration_minutes = db.Column(db.Integer, nullable=False, default=5)
    assigned_to_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    closed_day_policy = db.Column(db.String(30), nullable=False, default="PREVIOUS_WORKING_DAY")
    calendar_scope = db.Column(db.String(20), nullable=False, default="school")
    fixed_month = db.Column(db.Integer, nullable=True)
    fixed_day = db.Column(db.Integer, nullable=True)
    created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

    meter = db.relationship("Meter", backref=db.backref("reading_schedule", uselist=False))
    assigned_to = db.relationship("User")
    occurrences = db.relationship(
        "MeterReadingOccurrence", back_populates="schedule", cascade="all, delete-orphan",
        order_by="MeterReadingOccurrence.target_date",
    )


class MeterReadingRound(db.Model):
    """Définition d'une tournée de relevés."""
    __tablename__ = "meter_reading_rounds"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    is_active = db.Column(db.Boolean, nullable=False, default=True, server_default="1")
    default_assigned_to_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    estimated_duration_minutes = db.Column(db.Integer, nullable=True)
    created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

    default_assigned_to = db.relationship("User")
    members = db.relationship(
        "MeterReadingRoundMember", back_populates="round", cascade="all, delete-orphan",
        order_by="MeterReadingRoundMember.position",
    )
    occurrences = db.relationship(
        "MeterReadingRoundOccurrence", back_populates="round", cascade="all, delete-orphan",
    )


class MeterReadingRoundMember(db.Model):
    """Compteur/règle et ordre d'une tournée."""
    __tablename__ = "meter_reading_round_members"
    __table_args__ = (
        db.UniqueConstraint("round_id", "schedule_id", name="uq_meter_round_schedule"),
        db.UniqueConstraint("round_id", "position", name="uq_meter_round_position"),
    )

    id = db.Column(db.Integer, primary_key=True)
    round_id = db.Column(db.Integer, db.ForeignKey("meter_reading_rounds.id", ondelete="CASCADE"), nullable=False, index=True)
    schedule_id = db.Column(db.Integer, db.ForeignKey("meter_reading_schedules.id", ondelete="CASCADE"), nullable=False, index=True)
    position = db.Column(db.Integer, nullable=False)

    round = db.relationship("MeterReadingRound", back_populates="members")
    schedule = db.relationship("MeterReadingSchedule")


class MeterReadingRoundOccurrence(db.Model):
    """Occurrence opérationnelle d'une tournée à une date donnée."""
    __tablename__ = "meter_reading_round_occurrences"
    __table_args__ = (
        db.UniqueConstraint("round_id", "operational_date", name="uq_meter_round_occurrence_date"),
    )

    id = db.Column(db.Integer, primary_key=True)
    round_id = db.Column(db.Integer, db.ForeignKey("meter_reading_rounds.id", ondelete="CASCADE"), nullable=False, index=True)
    operational_date = db.Column(db.Date, nullable=False, index=True)
    status = db.Column(db.String(30), nullable=False, default="À FAIRE")
    created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))

    round = db.relationship("MeterReadingRound", back_populates="occurrences")
    occurrences = db.relationship("MeterReadingOccurrence", back_populates="round_occurrence")


class MeterReadingOccurrence(db.Model):
    """Une échéance unique, persistante et traitable."""
    __tablename__ = "meter_reading_occurrences"
    __table_args__ = (
        db.UniqueConstraint("schedule_id", "target_date", name="uq_meter_reading_occurrence_target"),
    )

    id = db.Column(db.Integer, primary_key=True)
    schedule_id = db.Column(db.Integer, db.ForeignKey("meter_reading_schedules.id", ondelete="CASCADE"), nullable=False, index=True)
    target_date = db.Column(db.Date, nullable=False, index=True)
    operational_date = db.Column(db.Date, nullable=False, index=True)
    status = db.Column(db.String(30), nullable=False, default="À FAIRE")
    assigned_to_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    meter_reading_id = db.Column(db.Integer, db.ForeignKey("meter_readings.id", ondelete="SET NULL"), nullable=True, index=True)
    round_occurrence_id = db.Column(db.Integer, db.ForeignKey("meter_reading_round_occurrences.id", ondelete="SET NULL"), nullable=True, index=True)
    processed_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    processed_at = db.Column(db.DateTime, nullable=True)
    no_reading_reason = db.Column(db.String(40), nullable=True)
    comment = db.Column(db.Text, nullable=True)
    created_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

    schedule = db.relationship("MeterReadingSchedule", back_populates="occurrences")
    assigned_to = db.relationship("User", foreign_keys=[assigned_to_id])
    processed_by = db.relationship("User", foreign_keys=[processed_by_id])
    meter_reading = db.relationship("MeterReading")
    round_occurrence = db.relationship("MeterReadingRoundOccurrence", back_populates="occurrences")


class MeterHeatingRegime(db.Model):
    """Régime daté d'un réseau de chauffage lié à un compteur."""
    __tablename__ = "meter_heating_regimes"
    __table_args__ = (db.CheckConstraint("regime in ('NORMAL', 'REDUCED', 'STOP')", name="ck_meter_heating_regime"),)
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    network_name = db.Column(db.String(120), nullable=False, default="Réseau principal")
    regime = db.Column(db.String(20), nullable=False)
    valid_from = db.Column(db.Date, nullable=False, index=True)
    valid_to = db.Column(db.Date, nullable=True, index=True)
    notes = db.Column(db.Text, nullable=True)
    meter = db.relationship("Meter", backref=db.backref("heating_regimes", cascade="all, delete-orphan", order_by="MeterHeatingRegime.valid_from"))


class MeterAlertRule(db.Model):
    """Règle C3 indépendante des seuils historiques de Meter."""
    __tablename__ = "meter_alert_rules"
    __table_args__ = (db.CheckConstraint("operator in ('>', '<', '>=', '<=')", name="ck_meter_alert_operator"),)
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    metric = db.Column(db.String(60), nullable=False)
    rule_type = db.Column(db.String(30), nullable=False, default="MANUAL_THRESHOLD", server_default="MANUAL_THRESHOLD")
    period = db.Column(db.String(10), nullable=False, default="DAY")
    context_filter = db.Column(db.String(40), nullable=True)
    operator = db.Column(db.String(2), nullable=False, default=">")
    threshold = db.Column(db.Float, nullable=False)
    level = db.Column(db.String(10), nullable=False, default="WARNING")
    is_active = db.Column(db.Boolean, nullable=False, default=True, index=True)
    min_comparable_intervals = db.Column(db.Integer, nullable=False, default=3)
    min_deviation_percent = db.Column(db.Float, nullable=False, default=30.0)
    low_consumption_threshold = db.Column(db.Float, nullable=True)
    detect_zero_or_low = db.Column(db.Boolean, nullable=False, default=False)
    meter = db.relationship("Meter", backref=db.backref("alert_rules", cascade="all, delete-orphan"))


class MeterAlert(db.Model):
    """Alerte persistée, dédupliquée tant qu'elle reste ouverte."""
    __tablename__ = "meter_alerts"
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    alert_type = db.Column(db.String(40), nullable=False)
    level = db.Column(db.String(10), nullable=False)
    metric = db.Column(db.String(60), nullable=False)
    period = db.Column(db.String(10), nullable=False)
    context_key = db.Column(db.String(120), nullable=True)
    period_start = db.Column(db.Date, nullable=True)
    period_end = db.Column(db.Date, nullable=True)
    observed_value = db.Column(db.Float, nullable=True)
    reference_value = db.Column(db.Float, nullable=True)
    deviation_percent = db.Column(db.Float, nullable=True)
    comparable_count = db.Column(db.Integer, nullable=True)
    status = db.Column(db.String(20), nullable=False, default="OPEN", index=True)
    opened_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    closed_at = db.Column(db.DateTime, nullable=True)
    conclusion = db.Column(db.String(30), nullable=True)
    comment = db.Column(db.Text, nullable=True)
    intervention_id = db.Column(db.Integer, db.ForeignKey("interventions.id", ondelete="SET NULL"), nullable=True, index=True)
    rule_id = db.Column(db.Integer, db.ForeignKey("meter_alert_rules.id", ondelete="SET NULL"), nullable=True, index=True)
    meter = db.relationship("Meter", backref=db.backref("alerts", cascade="all, delete-orphan"))
    intervention = db.relationship("Intervention", backref=db.backref("meter_alerts", lazy="dynamic"))
    evidence = db.relationship("MeterAlertEvidence", back_populates="alert", cascade="all, delete-orphan")


class MeterAlertEvidence(db.Model):
    __tablename__ = "meter_alert_evidence"
    __table_args__ = (db.UniqueConstraint("alert_id", "reading_start_id", "reading_end_id", name="uq_meter_alert_evidence_interval"),)
    id = db.Column(db.Integer, primary_key=True)
    alert_id = db.Column(db.Integer, db.ForeignKey("meter_alerts.id", ondelete="CASCADE"), nullable=False, index=True)
    reading_start_id = db.Column(db.Integer, db.ForeignKey("meter_readings.id", ondelete="SET NULL"), nullable=True)
    reading_end_id = db.Column(db.Integer, db.ForeignKey("meter_readings.id", ondelete="SET NULL"), nullable=True)
    observed_value = db.Column(db.Float, nullable=True)
    detected_at = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
    excluded_from_baseline = db.Column(db.Boolean, nullable=False, default=False)
    alert = db.relationship("MeterAlert", back_populates="evidence")


class GasConversion(db.Model):
    __tablename__ = "meter_gas_conversions"
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    coefficient_kwh_per_m3 = db.Column(db.Float, nullable=False)
    valid_from = db.Column(db.Date, nullable=False, index=True)
    valid_to = db.Column(db.Date, nullable=True, index=True)
    origin = db.Column(db.String(20), nullable=False, default="MANUAL")
    volume_m3 = db.Column(db.Float, nullable=True)
    billed_kwh = db.Column(db.Float, nullable=True)
    meter = db.relationship("Meter", backref=db.backref("gas_conversions", cascade="all, delete-orphan"))


class MeterTariff(db.Model):
    __tablename__ = "meter_tariffs"
    id = db.Column(db.Integer, primary_key=True)
    meter_id = db.Column(db.Integer, db.ForeignKey("meters.id", ondelete="CASCADE"), nullable=False, index=True)
    energy_type = db.Column(db.String(30), nullable=False)
    unit_price = db.Column(db.Float, nullable=False)
    unit = db.Column(db.String(20), nullable=False)
    valid_from = db.Column(db.Date, nullable=False, index=True)
    valid_to = db.Column(db.Date, nullable=True, index=True)
    meter = db.relationship("Meter", backref=db.backref("tariffs", cascade="all, delete-orphan"))


class Consumable(db.Model):
    """Consommable pour maintenance."""
    __tablename__ = "consumables"
    
    id = db.Column(db.Integer, primary_key=True)
    
    name = db.Column(db.String(200), nullable=False)
    reference = db.Column(db.String(100))
    category = db.Column(db.String(50))
    consumable_type = db.Column(db.String(30), nullable=False, default="OTHER", server_default="OTHER")
    
    quantity = db.Column(db.Integer, default=0)
    min_quantity = db.Column(db.Integer, default=0)
    target_quantity = db.Column(db.Integer, nullable=True)
    unit = db.Column(db.String(20), default="pièce")
    
    unit_price = db.Column(db.Float)
    supplier_id = db.Column(db.Integer, db.ForeignKey("companies.id"))
    
    storage_location = db.Column(db.String(100))
    
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    usages = db.relationship("ConsumableUsage", back_populates="consumable", lazy="dynamic")
    supplier = db.relationship("Company")
    
    @property
    def is_low_stock(self):
        return self.quantity <= self.min_quantity

    @property
    def quantity_to_order(self):
        target = self.target_quantity if self.target_quantity is not None else self.min_quantity
        return max((target or 0) - (self.quantity or 0), 0)
    
    def __repr__(self):
        return f"<Consumable {self.name}>"


class ConsumableUsage(db.Model):
    """Utilisation de consommable."""
    __tablename__ = "consumable_usages"
    
    id = db.Column(db.Integer, primary_key=True)
    
    consumable_id = db.Column(db.Integer, db.ForeignKey("consumables.id"))
    equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"))
    intervention_id = db.Column(db.Integer, db.ForeignKey("interventions.id"))
    
    quantity_used = db.Column(db.Integer, default=1)
    used_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    used_by_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    
    notes = db.Column(db.Text)
    
    consumable = db.relationship("Consumable", back_populates="usages")
    equipment = db.relationship("Equipment")
    intervention = db.relationship("Intervention")
    used_by = db.relationship("User")
    
    def __repr__(self):
        return f"<ConsumableUsage {self.consumable_id}x{self.quantity_used}>"


class EquipmentConsumable(db.Model):
    """Consommables compatibles avec un équipement."""
    __tablename__ = "equipment_consumables"
    __table_args__ = (
        db.UniqueConstraint("equipment_id", "consumable_id", name="uq_equipment_consumable_pair"),
    )
    
    id = db.Column(db.Integer, primary_key=True)
    
    equipment_category_id = db.Column(db.Integer, db.ForeignKey("equipment_categories.id"))
    equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"))
    consumable_id = db.Column(db.Integer, db.ForeignKey("consumables.id"))
    
    replacement_interval_days = db.Column(db.Integer)
    replacement_interval_hours = db.Column(db.Integer)
    replacement_interval_cycles = db.Column(db.Integer)
    
    notes = db.Column(db.Text)
    
    equipment_category = db.relationship("EquipmentCategory")
    equipment = db.relationship("Equipment")
    consumable = db.relationship("Consumable")
    
    def __repr__(self):
        return f"<EquipmentConsumable {self.equipment_id}-{self.consumable_id}>"


class PreventiveTask(db.Model):
    """Tâche préventive récurrente."""
    __tablename__ = "preventive_tasks"
    
    id = db.Column(db.Integer, primary_key=True)
    
    lot_id = db.Column(db.Integer, db.ForeignKey("lots.id"))
    category_id = db.Column(db.Integer, db.ForeignKey("equipment_categories.id"))
    
    task_number = db.Column(db.String(20))
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    location_pattern = db.Column(db.String(255))
    
    period_days = db.Column(db.Integer)
    duration_minutes = db.Column(db.Integer, default=30)
    
    has_contract = db.Column(db.Boolean, default=False)
    manager = db.Column(db.String(50))
    company_id = db.Column(db.Integer, db.ForeignKey("companies.id"))
    contract_id = db.Column(db.Integer, db.ForeignKey("contracts.id"), nullable=True)
    
    trigger_type = db.Column(db.String(20), default="time")
    meter_type_required = db.Column(db.String(50))
    meter_threshold = db.Column(db.Float)
    
    requires_room_access = db.Column(db.Boolean, default=True)
    allowed_time_slots = db.Column(db.String(50), default="always")
    
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    lot = db.relationship("Lot", backref=db.backref("preventive_tasks", lazy="dynamic"))
    category = db.relationship("EquipmentCategory", backref=db.backref("preventive_tasks", lazy="dynamic"))
    company = db.relationship("Company")
    contract = db.relationship("Contract")
    consumables_needed = db.relationship("PreventiveTaskConsumable", back_populates="task", lazy="dynamic")
    
    def __repr__(self):
        return f"<PreventiveTask {self.task_number}: {self.name}>"


class PreventiveTaskConsumable(db.Model):
    """Consommables nécessaires pour une tâche préventive."""
    __tablename__ = "preventive_task_consumables"
    
    id = db.Column(db.Integer, primary_key=True)
    
    task_id = db.Column(db.Integer, db.ForeignKey("preventive_tasks.id"))
    consumable_id = db.Column(db.Integer, db.ForeignKey("consumables.id"))
    
    quantity = db.Column(db.Integer, default=1)
    
    task = db.relationship("PreventiveTask", back_populates="consumables_needed")
    consumable = db.relationship("Consumable")
    
    def __repr__(self):
        return f"<PreventiveTaskConsumable {self.task_id}-{self.consumable_id}>"


class ScheduledTask(db.Model):
    """Tâche planifiée pour un équipement."""
    __tablename__ = "scheduled_tasks"
    
    id = db.Column(db.Integer, primary_key=True)
    
    preventive_task_id = db.Column(db.Integer, db.ForeignKey("preventive_tasks.id"))
    lot_task_id = db.Column(db.Integer, db.ForeignKey("lot_tasks.id"))
    equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"))
    room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"))
    company_id = db.Column(db.Integer, db.ForeignKey("companies.id"))
    contract_id = db.Column(db.Integer, db.ForeignKey("contracts.id"), nullable=True)
    
    scheduled_date = db.Column(db.Date)
    scheduled_start = db.Column(db.Time)
    scheduled_end = db.Column(db.Time)
    estimated_duration = db.Column(db.Integer)
    
    status = db.Column(db.String(20), default="planned")
    postpone_reason = db.Column(db.Text)
    
    assigned_to_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    
    completed_at = db.Column(db.DateTime)
    actual_duration = db.Column(db.Integer)
    
    intervention_id = db.Column(db.Integer, db.ForeignKey("interventions.id"))
    
    notes = db.Column(db.Text)
    suspension_reason = db.Column(db.String(500), nullable=True)
    suspended_at = db.Column(db.DateTime, nullable=True)
    room_name_snapshot = db.Column(db.String(200), nullable=True)
    lot_name_snapshot = db.Column(db.String(500), nullable=True)
    equipment_name_snapshot = db.Column(db.String(200), nullable=True)
    lifecycle_status_snapshot = db.Column(db.String(30), nullable=True)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    preventive_task = db.relationship("PreventiveTask", backref=db.backref("scheduled_tasks", lazy="dynamic"))
    lot_task = db.relationship("LotTask", backref=db.backref("scheduled_tasks", lazy="dynamic"))
    equipment = db.relationship("Equipment", backref=db.backref("scheduled_tasks", lazy="dynamic"))
    room = db.relationship("Room")
    assigned_to = db.relationship("User")
    company = db.relationship("Company")
    contract = db.relationship("Contract")
    intervention = db.relationship("Intervention")
    execution_segments = db.relationship(
        "TaskExecutionSegment", back_populates="scheduled_task",
        cascade="all, delete-orphan", order_by="TaskExecutionSegment.started_at",
    )
    
    @property
    def is_overdue(self):
        if self.status == "planned" and self.scheduled_date:
            return self.scheduled_date < date.today()
        return False
    
    def can_be_ignored(self):
        return self.status == 'planned'

    @property
    def realized_minutes(self):
        """Temps réellement saisi dans les segments d'exécution."""
        return sum(int(segment.duration_minutes or 0) for segment in self.execution_segments)

    @property
    def remaining_minutes(self):
        estimated = int(self.estimated_duration or 0)
        return max(estimated - self.realized_minutes, 0) if estimated else 0
    
    def ignore(self, reason=''):
        """Marque la tache comme ignoree."""
        self.status = 'ignored'
        self.postpone_reason = reason
        db.session.commit()
    
    def convert_to_intervention(self, user_id=None):
        """Convertit la tache planifiee en intervention."""
        from .maintenance import Intervention
        if self.intervention_id:
            return None  # Deja convertie
        
        title = ''
        if self.preventive_task:
            title = f"[{self.scheduled_date}] {self.preventive_task.name}"[:290]
        elif self.lot_task:
            title = f"[{self.scheduled_date}] {self.lot_task.tache or 'Tache lot'}"[:290]
        elif self.equipment:
            title = f"[{self.scheduled_date}] Tache {self.equipment.name}"[:290]
        else:
            title = f"Tache {self.id} du {self.scheduled_date}"
        
        interv = Intervention(
            title=title,
            description='',
            type='preventif',
            status='en_attente',
            priority='normale',
            equipment_id=self.equipment_id,
            room_id=self.room_id,
            scheduled_date=self.scheduled_date,
            estimated_duration=float(self.estimated_duration or 0),
        )
        db.session.add(interv)
        db.session.flush()
        self.intervention_id = interv.id
        self.status = 'converted'
        db.session.commit()
        return interv
    
    def __repr__(self):
        return f"<ScheduledTask {self.id}: {self.scheduled_date}>"


class TechnicianAvailability(db.Model):
    """Disponibilité du technicien."""
    __tablename__ = "technician_availability"
    
    id = db.Column(db.Integer, primary_key=True)
    
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    
    date = db.Column(db.Date)
    start_time = db.Column(db.Time)
    end_time = db.Column(db.Time)
    
    availability_type = db.Column(db.String(20), default="work")
    notes = db.Column(db.Text)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    
    user = db.relationship("User", backref=db.backref("availabilities", lazy="dynamic"))
    
    def __repr__(self):
        return f"<TechnicianAvailability {self.date} {self.availability_type}>"


class AdminTask(db.Model):
    """Tâche administrative récurrente."""
    __tablename__ = "admin_tasks"
    
    id = db.Column(db.Integer, primary_key=True)
    # Tâche personnelle facultative ; NULL conserve la compatibilité des
    # anciennes tâches administratives globales.
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True)
    
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    
    frequency = db.Column(db.String(20), default="daily")
    day_of_week = db.Column(db.Integer)
    day_of_month = db.Column(db.Integer)
    
    start_time = db.Column(db.Time)
    duration_minutes = db.Column(db.Integer, default=30)
    
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

    user = db.relationship("User", backref=db.backref("admin_tasks", lazy="dynamic"))
    
    def __repr__(self):
        return f"<AdminTask {self.name}>"


class ZoneAccessRule(db.Model):
    """Règle d'accès zone/salles."""
    __tablename__ = "zone_access_rules"
    
    id = db.Column(db.Integer, primary_key=True)
    
    zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"))
    zone_name = db.Column(db.String(100))
    
    blocking_room_ids = db.Column(db.Text)
    
    blocked_hours_start = db.Column(db.Time)
    blocked_hours_end = db.Column(db.Time)
    
    notes = db.Column(db.Text)
    
    zone = db.relationship("Zone")


class PlanningDay(db.Model):
    """Planification d'une journée."""
    __tablename__ = "planning_days"
    
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.Date, nullable=False, unique=True)
    
    # État de la journée
    is_working_day = db.Column(db.Boolean, default=True)
    is_holiday = db.Column(db.Boolean, default=False)
    is_vacation = db.Column(db.Boolean, default=False)
    
    # Notes
    notes = db.Column(db.Text)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    # Relations
    items = db.relationship("PlanningItem", back_populates="planning_day", cascade="all, delete-orphan")
    
    def __repr__(self):
        return f"<PlanningDay {self.date}>"


class PlanningItem(db.Model):
    """Élément planifié (intervention, formation, administrative)."""
    __tablename__ = "planning_items"
    
    id = db.Column(db.Integer, primary_key=True)
    planning_day_id = db.Column(db.Integer, db.ForeignKey("planning_days.id"))
    
    # Type d'élément
    item_type = db.Column(db.String(20), nullable=False)  # 'preventive', 'curative', 'admin', 'formation'
    
    # Références optionnelles
    scheduled_task_id = db.Column(db.Integer, db.ForeignKey("scheduled_tasks.id"))
    intervention_id = db.Column(db.Integer, db.ForeignKey("interventions.id"))
    admin_task_id = db.Column(db.Integer, db.ForeignKey("admin_tasks.id"))
    
    # Données de base (copiées pour affichage rapide)
    title = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    duration_minutes = db.Column(db.Integer, default=30)
    
    # Planification
    start_time = db.Column(db.Time)
    end_time = db.Column(db.Time)
    room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"))
    
    # Priorité (1=haute, 5=basse)
    priority = db.Column(db.Integer, default=3)
    
    # Regroupement
    group_id = db.Column(db.String(50))  # Pour grouper les tâches dans la même salle
    
    # Présence requise (entreprise externe mais technicien présent)
    requires_presence = db.Column(db.Boolean, default=False)
    
    # État
    status = db.Column(db.String(20), default="planned")  # planned, in_progress, completed, postponed
    postpone_reason = db.Column(db.Text)
    postponed_to_date = db.Column(db.Date)
    
    # Compteurs (pour relevés)
    meter_reading_id = db.Column(db.Integer)  # Référence sans FK (table créée plus tard)
    
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
    
    # Relations
    planning_day = db.relationship("PlanningDay", back_populates="items")
    scheduled_task = db.relationship("ScheduledTask")
    intervention = db.relationship("Intervention")
    admin_task = db.relationship("AdminTask")
    room = db.relationship("Room")
    
    @property
    def is_prioritary(self):
        """Une tâche est prioritaire si sa priorité est 1 ou 2."""
        return self.priority in (1, 2)
    
    def __repr__(self):
        return f"<PlanningItem {self.item_type}: {self.title}>"


class TaskExecutionSegment(db.Model):
    """Un segment de réalisation d'une tâche planifiée.

    Plusieurs segments peuvent appartenir à la même obligation. Cela permet
    de mettre en pause une tonte ou un contrôle sans perdre le temps déjà
    réalisé et sans créer une fausse tâche indépendante.
    """
    __tablename__ = "task_execution_segments"

    id = db.Column(db.Integer, primary_key=True)
    scheduled_task_id = db.Column(db.Integer, db.ForeignKey("scheduled_tasks.id", ondelete="CASCADE"), nullable=False, index=True)
    technician_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
    started_at = db.Column(db.DateTime, nullable=False)
    ended_at = db.Column(db.DateTime, nullable=True)
    duration_minutes = db.Column(db.Integer, nullable=False, default=0)
    status = db.Column(db.String(20), nullable=False, default="active")
    pause_reason = db.Column(db.String(80), nullable=True)
    note = db.Column(db.Text, nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)

    scheduled_task = db.relationship("ScheduledTask", back_populates="execution_segments")
    technician = db.relationship("User")

    def close(self, ended_at, reason=None):
        self.ended_at = ended_at
        self.status = "paused" if reason else "completed"
        self.pause_reason = reason
        self.duration_minutes = max(int((ended_at - self.started_at).total_seconds() // 60), 0)
