from datetime import datetime, date, timedelta
import json
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from . import db

service_professionals = db.Table(
    'service_professionals',
    db.Column('service_id', db.Integer, db.ForeignKey('service.id'), primary_key=True),
    db.Column('professional_id', db.Integer, db.ForeignKey('professional_profile.id'), primary_key=True)
)


class TimestampMixin:
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)



class Institution(TimestampMixin, db.Model):
    __tablename__ = 'institution'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(180), nullable=False, index=True)
    cuit = db.Column(db.String(30), unique=True, index=True)
    domicilio = db.Column(db.String(220))
    ciudad = db.Column(db.String(120))
    provincia = db.Column(db.String(120))
    telefono = db.Column(db.String(80))
    responsable = db.Column(db.String(160))
    situacion_juridica = db.Column(db.String(120))
    nro_habilitacion = db.Column(db.String(120))
    expediente_autorizacion = db.Column(db.String(160))
    email = db.Column(db.String(160))
    telefono_responsable = db.Column(db.String(80))
    logo_path = db.Column(db.String(255))
    active = db.Column(db.Boolean, default=True, index=True)

    users = db.relationship('User', back_populates='institution')
    professionals = db.relationship('ProfessionalProfile', back_populates='institution')
    patients = db.relationship('Patient', back_populates='institution')
    branches = db.relationship('InstitutionBranch', back_populates='institution', cascade='all, delete-orphan')

    @property
    def display_name(self):
        return self.name or f'Institución #{self.id}'


class InstitutionBranch(TimestampMixin, db.Model):
    __tablename__ = 'institution_branch'
    id = db.Column(db.Integer, primary_key=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), nullable=False, index=True)
    name = db.Column(db.String(160), nullable=False)
    address = db.Column(db.String(220))
    province = db.Column(db.String(120), index=True)
    city = db.Column(db.String(120), index=True)
    phone = db.Column(db.String(80))
    email = db.Column(db.String(160))
    notes = db.Column(db.Text)
    active = db.Column(db.Boolean, default=True, index=True)

    institution = db.relationship('Institution', back_populates='branches')
    appointments = db.relationship('Appointment', back_populates='branch')

    @property
    def display_name(self):
        return self.name or f'Sede #{self.id}'

    @property
    def full_address(self):
        bits = [self.address, self.city, self.province]
        return ', '.join([b for b in bits if b])


class User(UserMixin, TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    full_name = db.Column(db.String(150), nullable=False)
    email = db.Column(db.String(150), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    role = db.Column(db.String(30), nullable=False, default='professional')
    is_active_user = db.Column(db.Boolean, default=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    institution = db.relationship('Institution', back_populates='users')
    professional_profile = db.relationship('ProfessionalProfile', back_populates='user', uselist=False)
    issued_prescriptions = db.relationship('Prescription', back_populates='issued_by_user', foreign_keys='Prescription.issued_by_user_id')

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    @property
    def is_active(self):
        return self.is_active_user


class Specialty(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), unique=True, nullable=False)
    active = db.Column(db.Boolean, default=True)


class AppSetting(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(100), unique=True, nullable=False)
    value = db.Column(db.Text)


class ProfessionalProfile(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), unique=True)
    display_name = db.Column(db.String(150), nullable=False)
    specialty = db.Column(db.String(150), nullable=False)
    bio = db.Column(db.Text)
    location = db.Column(db.String(150), default='Consultorio Central')
    phone = db.Column(db.String(50))
    contact_email = db.Column(db.String(150))
    is_bookable = db.Column(db.Boolean, default=True)
    color = db.Column(db.String(20), default='#0d6efd')
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    source_mode = db.Column(db.String(20), default='manual')
    matricula = db.Column(db.String(100))
    profession_name = db.Column(db.String(150))
    jurisdiction_name = db.Column(db.String(150))
    state_name = db.Column(db.String(120))
    province = db.Column(db.String(120))
    municipality = db.Column(db.String(120))
    city = db.Column(db.String(120))
    address = db.Column(db.String(200))
    address_number = db.Column(db.String(30))
    specialty_id = db.Column(db.Integer, db.ForeignKey('specialty.id'))

    institution = db.relationship('Institution', back_populates='professionals')
    user = db.relationship('User', back_populates='professional_profile')
    specialty_manual = db.relationship('Specialty')
    working_hours = db.relationship('WorkingHour', back_populates='professional', cascade='all, delete-orphan')
    leaves = db.relationship('Leave', back_populates='professional', cascade='all, delete-orphan')
    appointments = db.relationship('Appointment', back_populates='professional')
    services = db.relationship('Service', secondary=service_professionals, back_populates='professionals')
    prescriptions = db.relationship('Prescription', back_populates='professional')

    @property
    def full_address(self):
        bits = [self.address, self.address_number, self.city, self.municipality, self.province]
        return ', '.join([b for b in bits if b])


class Service(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False, unique=True)
    description = db.Column(db.Text)
    duration_minutes = db.Column(db.Integer, nullable=False, default=30)
    buffer_before_minutes = db.Column(db.Integer, nullable=False, default=0)
    buffer_after_minutes = db.Column(db.Integer, nullable=False, default=0)
    price = db.Column(db.Float, default=0)
    mode = db.Column(db.String(30), default='Presencial')
    active = db.Column(db.Boolean, default=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)
    min_notice_hours = db.Column(db.Integer, default=4)
    cancel_notice_hours = db.Column(db.Integer, default=12)
    allow_online_cancel = db.Column(db.Boolean, default=True)

    institution = db.relationship('Institution')
    appointments = db.relationship('Appointment', back_populates='service')
    professionals = db.relationship('ProfessionalProfile', secondary=service_professionals, back_populates='services')


class WorkingHour(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False)
    weekday = db.Column(db.Integer, nullable=False)
    start_time = db.Column(db.Time, nullable=False)
    end_time = db.Column(db.Time, nullable=False)
    is_active = db.Column(db.Boolean, default=True)

    professional = db.relationship('ProfessionalProfile', back_populates='working_hours')


class Leave(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False)
    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=False)
    reason = db.Column(db.String(200), nullable=False)

    professional = db.relationship('ProfessionalProfile', back_populates='leaves')


class ObraSocialPageSnapshot(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tipo = db.Column(db.Integer, unique=True, nullable=False)
    categoria_oficial = db.Column(db.String(255))
    origen_datos = db.Column(db.String(255))
    status = db.Column(db.String(50), default='never_synced')
    row_count = db.Column(db.Integer, default=0)
    last_hash = db.Column(db.String(128))
    last_error = db.Column(db.Text)
    last_synced_at = db.Column(db.DateTime)


class ObraSocialCatalog(TimestampMixin, db.Model):
    __tablename__ = 'obras_sociales_catalogo'
    id = db.Column(db.Integer, primary_key=True)
    tipo = db.Column(db.Integer, index=True)
    categoria_oficial = db.Column(db.String(255), index=True)
    rnas = db.Column(db.String(50), index=True)
    denominacion = db.Column(db.String(255), nullable=False, index=True)
    domicilio = db.Column(db.String(255))
    localidad = db.Column(db.String(255), index=True)
    telefono = db.Column(db.String(120))
    linea_gratuita = db.Column(db.String(120))
    habilitada_opciones = db.Column(db.String(50), index=True)
    vigente = db.Column(db.Boolean, default=True, index=True)
    row_hash = db.Column(db.String(128))
    last_seen_at = db.Column(db.DateTime)
    page_snapshot_id = db.Column(db.Integer, db.ForeignKey('obra_social_page_snapshot.id'))


class Patient(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nombre = db.Column(db.String(120), nullable=False)
    apellido = db.Column(db.String(120), nullable=False)
    documento = db.Column(db.String(40), unique=True, nullable=False, index=True)
    tipo_documento = db.Column(db.String(30), default='DNI')
    fecha_nacimiento = db.Column(db.String(20))
    genero = db.Column(db.String(30))
    telefono = db.Column(db.String(60))
    email = db.Column(db.String(150))
    calle = db.Column(db.String(150))
    numero = db.Column(db.String(30))
    piso = db.Column(db.String(30))
    provincia = db.Column(db.String(120))
    municipio = db.Column(db.String(120))
    localidad = db.Column(db.String(120))
    cp = db.Column(db.String(20))
    obra_social_id = db.Column(db.Integer, db.ForeignKey('obras_sociales_catalogo.id'))
    afiliado_nro = db.Column(db.String(80))
    observaciones = db.Column(db.Text)
    nombre_contacto = db.Column(db.String(150))
    telefono_contacto = db.Column(db.String(60))
    estado = db.Column(db.String(30), default='Activo', index=True)
    fecha_creacion = db.Column(db.DateTime, default=datetime.utcnow)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    institution = db.relationship('Institution', back_populates='patients')
    obra_social = db.relationship('ObraSocialCatalog')
    appointments = db.relationship('Appointment', back_populates='patient')
    prescriptions = db.relationship('Prescription', back_populates='patient')

    @property
    def nombre_completo(self):
        return f"{self.apellido}, {self.nombre}"

    @property
    def domicilio_completo(self):
        bits = [self.calle, self.numero, self.piso, self.localidad, self.municipio, self.provincia]
        return ', '.join([b for b in bits if b])


class Appointment(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    service_id = db.Column(db.Integer, db.ForeignKey('service.id'), nullable=False)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False)
    patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'))
    client_name = db.Column(db.String(150), nullable=False)
    client_email = db.Column(db.String(150), nullable=False)
    client_phone = db.Column(db.String(50))
    notes = db.Column(db.Text)
    appointment_date = db.Column(db.Date, nullable=False)
    start_time = db.Column(db.Time, nullable=False)
    end_time = db.Column(db.Time, nullable=False)
    status = db.Column(db.String(30), default='confirmed')
    booking_source = db.Column(db.String(50), default='website')
    public_token = db.Column(db.String(120), unique=True, nullable=False)
    internal_notes = db.Column(db.Text)
    reminder_sent = db.Column(db.Boolean, default=False)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)
    branch_id = db.Column(db.Integer, db.ForeignKey('institution_branch.id'), index=True)

    institution = db.relationship('Institution')
    branch = db.relationship('InstitutionBranch', back_populates='appointments')
    service = db.relationship('Service', back_populates='appointments')
    professional = db.relationship('ProfessionalProfile', back_populates='appointments')
    patient = db.relationship('Patient', back_populates='appointments')

    @property
    def starts_at(self):
        return datetime.combine(self.appointment_date, self.start_time)

    @property
    def ends_at(self):
        return datetime.combine(self.appointment_date, self.end_time)

    @property
    def is_future(self):
        return self.starts_at >= datetime.now()


class Prescription(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False, index=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False, index=True)
    issued_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    issued_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, index=True)
    prescription_date = db.Column(db.Date, nullable=False, default=date.today)
    expires_at = db.Column(db.Date, nullable=False)
    status = db.Column(db.String(30), default='Activa', nullable=False, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    legal_number = db.Column(db.String(40), unique=True, nullable=False, index=True)
    cuir = db.Column(db.String(50), unique=True, nullable=False, index=True)
    platform_number = db.Column(db.String(4))
    repository_number = db.Column(db.String(4))
    jurisdiction_code = db.Column(db.String(2))
    prescription_type = db.Column(db.String(2), default='01')
    prescription_subtype = db.Column(db.String(2), default='02')
    prescription_group = db.Column(db.String(25))
    item_number = db.Column(db.String(2), default='01')
    document_kind = db.Column(db.String(30), default='recipe', nullable=False, index=True)
    document_title = db.Column(db.String(255))
    document_body = db.Column(db.Text)
    portal_visible = db.Column(db.Boolean, default=True, nullable=False)

    patient_full_name = db.Column(db.String(150), nullable=False)
    patient_document = db.Column(db.String(40), nullable=False)
    patient_birth_date = db.Column(db.String(20))
    patient_gender = db.Column(db.String(30))
    patient_obra_social = db.Column(db.String(255))
    patient_plan = db.Column(db.String(120))

    professional_display_name = db.Column(db.String(150), nullable=False)
    professional_profession_name = db.Column(db.String(150))
    professional_specialty = db.Column(db.String(150))
    professional_matricula = db.Column(db.String(120))
    professional_jurisdiction_name = db.Column(db.String(150))
    professional_address = db.Column(db.String(255))

    medication_generic_name = db.Column(db.String(255), nullable=False)
    medication_presentation = db.Column(db.String(255))
    pharmaceutical_form = db.Column(db.String(255))
    quantity_units = db.Column(db.String(120))
    diagnosis = db.Column(db.String(255))
    dosage_instructions = db.Column(db.Text)
    barcode_value = db.Column(db.String(64))
    platform_registry_legend = db.Column(db.String(255))

    internal_notes = db.Column(db.Text)
    suspended_at = db.Column(db.DateTime)
    suspended_reason = db.Column(db.String(255))

    institution = db.relationship('Institution')
    patient = db.relationship('Patient', back_populates='prescriptions')
    professional = db.relationship('ProfessionalProfile', back_populates='prescriptions')
    issued_by_user = db.relationship('User', back_populates='issued_prescriptions', foreign_keys=[issued_by_user_id])

    @property
    def computed_status(self):
        if (self.status or '').lower() in {'suspendida', 'anulada'}:
            return self.status
        if self.expires_at and date.today() > self.expires_at:
            return 'Vencida'
        return self.status or 'Activa'

    @property
    def patient_document_last4(self):
        digits = ''.join(ch for ch in (self.patient_document or '') if ch.isdigit())
        return digits[-4:] if len(digits) >= 4 else digits


class BackupRecord(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    scope = db.Column(db.String(30), nullable=False, default='full', index=True)
    provider = db.Column(db.String(30), nullable=False, default='local')
    filename = db.Column(db.String(255), nullable=False)
    relative_path = db.Column(db.String(255))
    target_path = db.Column(db.String(255))
    status = db.Column(db.String(30), nullable=False, default='created', index=True)
    size_bytes = db.Column(db.Integer, default=0)
    sha256 = db.Column(db.String(128))
    notes = db.Column(db.Text)


class FrontendBlock(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    section = db.Column(db.String(80), nullable=False, index=True)
    key = db.Column(db.String(80), default='default', index=True)
    title = db.Column(db.String(255))
    subtitle = db.Column(db.String(255))
    body = db.Column(db.Text)
    icon = db.Column(db.String(80))
    image_path = db.Column(db.String(255))
    link_url = db.Column(db.String(255))
    link_text = db.Column(db.String(120))
    sort_order = db.Column(db.Integer, default=0, index=True)
    is_active = db.Column(db.Boolean, default=True, index=True)
    extra_json = db.Column(db.Text)


class ContactInquiry(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False)
    email = db.Column(db.String(150), nullable=False, index=True)
    phone = db.Column(db.String(60))
    detail = db.Column(db.Text, nullable=False)
    status = db.Column(db.String(30), default='No Leido', index=True)
    source = db.Column(db.String(50), default='frontend')
    read_at = db.Column(db.DateTime)


class ClinicalRecord(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False, unique=True, index=True)
    legajo_number = db.Column(db.String(80), nullable=False, unique=True, index=True)
    opened_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    status = db.Column(db.String(30), default='Activo', index=True)
    confidentiality_level = db.Column(db.String(30), default='Restringido')
    retention_until = db.Column(db.Date)
    notes = db.Column(db.Text)
    summary_payload = db.Column(db.Text)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    patient = db.relationship('Patient', backref=db.backref('clinical_record', uselist=False, cascade='all, delete-orphan'))
    episodes = db.relationship('ClinicalEpisode', back_populates='record', cascade='all, delete-orphan', order_by='ClinicalEpisode.started_at.desc()')
    entries = db.relationship('ClinicalEntry', back_populates='record', cascade='all, delete-orphan', order_by='ClinicalEntry.entry_datetime.desc()')


class ClinicalEpisode(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    record_id = db.Column(db.Integer, db.ForeignKey('clinical_record.id'), nullable=False, index=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False, index=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    title = db.Column(db.String(180), nullable=False)
    specialty_name = db.Column(db.String(150))
    reason = db.Column(db.String(255))
    diagnosis_summary = db.Column(db.String(255))
    care_level = db.Column(db.String(80), default='Ambulatorio')
    status = db.Column(db.String(30), default='Abierto', index=True)
    visibility_scope = db.Column(db.String(30), default='Institucional')
    started_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, index=True)
    closed_at = db.Column(db.DateTime)
    notes = db.Column(db.Text)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    record = db.relationship('ClinicalRecord', back_populates='episodes')
    patient = db.relationship('Patient', backref=db.backref('clinical_episodes', lazy='dynamic'))
    created_by_user = db.relationship('User', backref=db.backref('clinical_episodes_created', lazy='dynamic'))
    members = db.relationship('ClinicalEpisodeMember', back_populates='episode', cascade='all, delete-orphan', order_by='ClinicalEpisodeMember.created_at.asc()')
    entries = db.relationship('ClinicalEntry', back_populates='episode', order_by='ClinicalEntry.entry_datetime.desc()')


class ClinicalEpisodeTemplate(TimestampMixin, db.Model):
    """Template reutilizable para abrir episodios clínicos frecuentes.

    No representa una nueva Historia Clínica: solo guarda la estructura de apertura
    de un episodio para que el profesional pueda reutilizarla en otros pacientes.
    """
    id = db.Column(db.Integer, primary_key=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False, index=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
    title = db.Column(db.String(180), nullable=False, index=True)
    category = db.Column(db.String(120), default='General', index=True)
    specialty_name = db.Column(db.String(150), index=True)
    reason = db.Column(db.String(255))
    diagnosis_summary = db.Column(db.String(255))
    care_level = db.Column(db.String(80), default='Ambulatorio')
    visibility_scope = db.Column(db.String(30), default='Institucional')
    notes = db.Column(db.Text)
    source_episode_id = db.Column(db.Integer, db.ForeignKey('clinical_episode.id'), index=True)
    usage_count = db.Column(db.Integer, default=0)
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    professional = db.relationship('ProfessionalProfile', backref=db.backref('clinical_episode_templates', lazy='dynamic', cascade='all, delete-orphan'))
    created_by_user = db.relationship('User', backref=db.backref('clinical_episode_templates_created', lazy='dynamic'))
    source_episode = db.relationship('ClinicalEpisode', backref=db.backref('saved_as_episode_templates', lazy='dynamic'))

    def to_payload(self):
        return {
            'id': self.id,
            'title': self.title or '',
            'category': self.category or 'General',
            'specialty_name': self.specialty_name or '',
            'reason': self.reason or '',
            'diagnosis_summary': self.diagnosis_summary or '',
            'care_level': self.care_level or 'Ambulatorio',
            'visibility_scope': self.visibility_scope or 'Institucional',
            'notes': self.notes or '',
            'usage_count': self.usage_count or 0,
        }


class ClinicalEpisodeMember(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    episode_id = db.Column(db.Integer, db.ForeignKey('clinical_episode.id'), nullable=False, index=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, index=True)
    added_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    role_label = db.Column(db.String(120), default='Integrante')
    can_view = db.Column(db.Boolean, default=True)
    can_write = db.Column(db.Boolean, default=False)
    can_sign = db.Column(db.Boolean, default=False)
    can_export = db.Column(db.Boolean, default=False)
    is_active = db.Column(db.Boolean, default=True, index=True)
    joined_at = db.Column(db.DateTime, default=datetime.utcnow)
    left_at = db.Column(db.DateTime)

    episode = db.relationship('ClinicalEpisode', back_populates='members')
    user = db.relationship('User', foreign_keys=[user_id], backref=db.backref('clinical_episode_memberships', lazy='dynamic'))
    added_by_user = db.relationship('User', foreign_keys=[added_by_user_id], backref=db.backref('clinical_episode_members_added', lazy='dynamic'))


class ClinicalEntry(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    record_id = db.Column(db.Integer, db.ForeignKey('clinical_record.id'), nullable=False, index=True)
    episode_id = db.Column(db.Integer, db.ForeignKey('clinical_episode.id'), index=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False, index=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False, index=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    last_edited_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    entry_datetime = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, index=True)
    folio_number = db.Column(db.Integer, nullable=False, index=True)
    specialty_name = db.Column(db.String(150))
    encounter_type = db.Column(db.String(80), default='Evolución médica')
    diagnosis_text = db.Column(db.String(255))
    chief_complaint = db.Column(db.String(255))
    provisional_diagnosis = db.Column(db.String(255))
    specialty_template = db.Column(db.String(80), default='general_medicine', index=True)
    cie10_code = db.Column(db.String(20), index=True)
    snomed_term = db.Column(db.String(255))
    snomed_code = db.Column(db.String(50), index=True)
    subjective = db.Column(db.Text)
    objective = db.Column(db.Text)
    assessment = db.Column(db.Text)
    plan = db.Column(db.Text)
    treatment = db.Column(db.Text)
    study_results = db.Column(db.Text)
    vitals_payload = db.Column(db.Text)
    structured_payload = db.Column(db.Text)
    consent_reference = db.Column(db.String(120))
    entry_status = db.Column(db.String(30), default='Firmado', index=True)
    locked_at = db.Column(db.DateTime)
    signed_name = db.Column(db.String(150))
    signed_hash = db.Column(db.String(128))
    signature_payload = db.Column(db.Text)
    visibility_scope = db.Column(db.String(30), default='Institucional')
    edit_revision = db.Column(db.Integer, default=1)
    last_edited_at = db.Column(db.DateTime)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    record = db.relationship('ClinicalRecord', back_populates='entries')
    episode = db.relationship('ClinicalEpisode', back_populates='entries')
    patient = db.relationship('Patient', backref=db.backref('clinical_entries', lazy='dynamic'))
    professional = db.relationship('ProfessionalProfile', backref=db.backref('clinical_entries', lazy='dynamic'))
    created_by_user = db.relationship('User', foreign_keys=[created_by_user_id], backref=db.backref('clinical_entries_created', lazy='dynamic'))
    last_edited_by_user = db.relationship('User', foreign_keys=[last_edited_by_user_id], backref=db.backref('clinical_entries_edited', lazy='dynamic'))
    attachments = db.relationship('ClinicalAttachment', back_populates='entry', cascade='all, delete-orphan')


class ClinicalEntryTemplate(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    professional_id = db.Column(db.Integer, db.ForeignKey('professional_profile.id'), nullable=False, index=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
    title = db.Column(db.String(180), nullable=False, index=True)
    category = db.Column(db.String(120), default='General', index=True)
    specialty_name = db.Column(db.String(150), index=True)
    specialty_template = db.Column(db.String(80), default='general_medicine', index=True)
    encounter_type = db.Column(db.String(80), default='Evolución médica')
    chief_complaint = db.Column(db.String(255))
    provisional_diagnosis = db.Column(db.String(255))
    diagnosis_text = db.Column(db.String(255))
    cie10_code = db.Column(db.String(20))
    snomed_term = db.Column(db.String(255))
    snomed_code = db.Column(db.String(50))
    subjective = db.Column(db.Text)
    objective = db.Column(db.Text)
    assessment = db.Column(db.Text)
    plan = db.Column(db.Text)
    treatment = db.Column(db.Text)
    study_results = db.Column(db.Text)
    vitals_payload = db.Column(db.Text)
    structured_payload = db.Column(db.Text)
    consent_reference = db.Column(db.String(120))
    visibility_scope = db.Column(db.String(30), default='Institucional')
    source_entry_id = db.Column(db.Integer, db.ForeignKey('clinical_entry.id'), index=True)
    usage_count = db.Column(db.Integer, default=0)
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    professional = db.relationship('ProfessionalProfile', backref=db.backref('clinical_entry_templates', lazy='dynamic', cascade='all, delete-orphan'))
    created_by_user = db.relationship('User', backref=db.backref('clinical_templates_created', lazy='dynamic'))
    source_entry = db.relationship('ClinicalEntry', backref=db.backref('saved_as_templates', lazy='dynamic'))

    def _safe_json(self, raw):
        try:
            payload = json.loads(raw or '{}')
            return payload if isinstance(payload, dict) else {}
        except Exception:
            return {}

    def to_payload(self):
        return {
            'id': self.id,
            'title': self.title,
            'category': self.category or 'General',
            'specialty_name': self.specialty_name or '',
            'specialty_template': self.specialty_template or 'general_medicine',
            'encounter_type': self.encounter_type or 'Evolución médica',
            'chief_complaint': self.chief_complaint or '',
            'provisional_diagnosis': self.provisional_diagnosis or '',
            'diagnosis_text': self.diagnosis_text or '',
            'cie10_code': self.cie10_code or '',
            'snomed_term': self.snomed_term or '',
            'snomed_code': self.snomed_code or '',
            'subjective': self.subjective or '',
            'objective': self.objective or '',
            'assessment': self.assessment or '',
            'plan': self.plan or '',
            'treatment': self.treatment or '',
            'study_results': self.study_results or '',
            'vitals': self._safe_json(self.vitals_payload),
            'structured': self._safe_json(self.structured_payload),
            'consent_reference': self.consent_reference or '',
            'visibility_scope': self.visibility_scope or 'Institucional',
            'usage_count': self.usage_count or 0,
        }


class ClinicalAttachment(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    entry_id = db.Column(db.Integer, db.ForeignKey('clinical_entry.id'), nullable=False, index=True)
    filename = db.Column(db.String(255), nullable=False)
    file_path = db.Column(db.String(255), nullable=False)
    mime_type = db.Column(db.String(120))
    is_patient_visible = db.Column(db.Boolean, default=False)

    entry = db.relationship('ClinicalEntry', back_populates='attachments')


class AccountingEntry(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    entry_date = db.Column(db.Date, nullable=False, default=date.today, index=True)
    entry_type = db.Column(db.String(20), nullable=False, index=True)  # ingreso / egreso
    category = db.Column(db.String(120), nullable=False, index=True)
    description = db.Column(db.String(255), nullable=False)
    amount = db.Column(db.Float, nullable=False, default=0)
    tax_profile = db.Column(db.String(80))
    receipt_type = db.Column(db.String(40))
    receipt_number = db.Column(db.String(80), index=True)
    counterparty_name = db.Column(db.String(150))
    counterparty_cuit = db.Column(db.String(20))
    payment_method = db.Column(db.String(60))
    status = db.Column(db.String(30), default='Registrado', index=True)
    notes = db.Column(db.Text)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    created_by_user = db.relationship('User', backref=db.backref('accounting_entries_created', lazy='dynamic'))


class SaasPlan(TimestampMixin, db.Model):
    __tablename__ = 'saas_plan'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(140), nullable=False, unique=True)
    description = db.Column(db.Text)
    monthly_price = db.Column(db.Float, nullable=False, default=0)
    price_per_appointment = db.Column(db.Float, nullable=False, default=0)
    included_professionals = db.Column(db.Integer, default=1)
    extra_professional_price = db.Column(db.Float, default=0)
    included_appointments = db.Column(db.Integer, default=0)
    active = db.Column(db.Boolean, default=True, index=True)


class InstitutionSubscription(TimestampMixin, db.Model):
    __tablename__ = 'institution_subscription'
    id = db.Column(db.Integer, primary_key=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), nullable=False, index=True)
    plan_id = db.Column(db.Integer, db.ForeignKey('saas_plan.id'), nullable=False, index=True)
    start_date = db.Column(db.Date, default=date.today, nullable=False)
    billing_day = db.Column(db.Integer, default=1)
    status = db.Column(db.String(30), default='activa', index=True)
    notes = db.Column(db.Text)
    institution = db.relationship('Institution', backref=db.backref('subscriptions', lazy='dynamic'))
    plan = db.relationship('SaasPlan', backref=db.backref('subscriptions', lazy='dynamic'))


class SaasInvoice(TimestampMixin, db.Model):
    __tablename__ = 'saas_invoice'
    id = db.Column(db.Integer, primary_key=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), nullable=False, index=True)
    subscription_id = db.Column(db.Integer, db.ForeignKey('institution_subscription.id'), index=True)
    period = db.Column(db.String(7), nullable=False, index=True)  # YYYY-MM
    issue_date = db.Column(db.Date, default=date.today, nullable=False)
    due_date = db.Column(db.Date, nullable=False)
    monthly_amount = db.Column(db.Float, default=0)
    appointment_count = db.Column(db.Integer, default=0)
    appointment_amount = db.Column(db.Float, default=0)
    professional_count = db.Column(db.Integer, default=0)
    extra_professional_amount = db.Column(db.Float, default=0)
    total_amount = db.Column(db.Float, default=0)
    paid_amount = db.Column(db.Float, default=0)
    status = db.Column(db.String(30), default='pendiente', index=True)
    payment_method = db.Column(db.String(40))
    mercadopago_preference_id = db.Column(db.String(160), index=True)
    mercadopago_payment_id = db.Column(db.String(160), index=True)
    mercadopago_status = db.Column(db.String(80), index=True)
    mercadopago_status_detail = db.Column(db.String(160))
    mercadopago_init_point = db.Column(db.String(600))
    mercadopago_sandbox_init_point = db.Column(db.String(600))
    mercadopago_payload = db.Column(db.Text)
    notes = db.Column(db.Text)
    institution = db.relationship('Institution', backref=db.backref('saas_invoices', lazy='dynamic'))
    subscription = db.relationship('InstitutionSubscription', backref=db.backref('invoices', lazy='dynamic'))

    @property
    def debt_amount(self):
        return max((self.total_amount or 0) - (self.paid_amount or 0), 0)


class SaasPayment(TimestampMixin, db.Model):
    __tablename__ = 'saas_payment'
    id = db.Column(db.Integer, primary_key=True)
    invoice_id = db.Column(db.Integer, db.ForeignKey('saas_invoice.id'), nullable=False, index=True)
    payment_date = db.Column(db.Date, default=date.today, nullable=False)
    amount = db.Column(db.Float, nullable=False, default=0)
    method = db.Column(db.String(40), default='efectivo')
    reference = db.Column(db.String(160))
    status = db.Column(db.String(40), default='registrado', index=True)
    mercadopago_payment_id = db.Column(db.String(160), index=True)
    mercadopago_status = db.Column(db.String(80))
    mercadopago_status_detail = db.Column(db.String(160))
    mercadopago_payload = db.Column(db.Text)
    notes = db.Column(db.Text)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    invoice = db.relationship('SaasInvoice', backref=db.backref('payments', lazy='dynamic'))
    created_by_user = db.relationship('User')


class AuditLog(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    actor_email = db.Column(db.String(150))
    action = db.Column(db.String(120), nullable=False)
    entity_type = db.Column(db.String(80), nullable=False)
    entity_id = db.Column(db.String(50))
    details = db.Column(db.Text)


class ErrorLog(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    actor_email = db.Column(db.String(150))
    route = db.Column(db.String(255))
    function_name = db.Column(db.String(120))
    error_type = db.Column(db.String(120))
    message = db.Column(db.Text)
    stacktrace = db.Column(db.Text)
    extra = db.Column(db.Text)



class ChatConversation(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(180))
    is_group = db.Column(db.Boolean, default=False, index=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

    institution = db.relationship('Institution')
    created_by_user = db.relationship('User', backref=db.backref('chat_conversations_created', lazy='dynamic'))
    members = db.relationship('ChatConversationMember', back_populates='conversation', cascade='all, delete-orphan')
    messages = db.relationship('ChatMessage', back_populates='conversation', cascade='all, delete-orphan', order_by='ChatMessage.created_at.asc()')

    @property
    def display_title(self):
        return self.title or ('Grupo' if self.is_group else 'Chat privado')


class ChatConversationMember(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    conversation_id = db.Column(db.Integer, db.ForeignKey('chat_conversation.id'), nullable=False, index=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, index=True)
    is_admin = db.Column(db.Boolean, default=False)
    last_read_at = db.Column(db.DateTime)

    conversation = db.relationship('ChatConversation', back_populates='members')
    user = db.relationship('User', backref=db.backref('chat_memberships', lazy='dynamic'))


class ChatMessage(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    conversation_id = db.Column(db.Integer, db.ForeignKey('chat_conversation.id'), nullable=False, index=True)
    sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, index=True)
    body = db.Column(db.Text)
    system_flag = db.Column(db.Boolean, default=False)

    conversation = db.relationship('ChatConversation', back_populates='messages')
    sender = db.relationship('User', backref=db.backref('chat_messages_sent', lazy='dynamic'))
    attachments = db.relationship('ChatAttachment', back_populates='message', cascade='all, delete-orphan')


class ChatAttachment(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    message_id = db.Column(db.Integer, db.ForeignKey('chat_message.id'), nullable=False, index=True)
    filename = db.Column(db.String(255), nullable=False)
    file_path = db.Column(db.String(255), nullable=False)
    mime_type = db.Column(db.String(120))
    file_size = db.Column(db.Integer)

    message = db.relationship('ChatMessage', back_populates='attachments')

class WhatsappBotRule(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    trigger = db.Column(db.String(120), nullable=False, index=True)
    title = db.Column(db.String(180))
    response_html = db.Column(db.Text, nullable=False)
    match_mode = db.Column(db.String(30), default='contains')
    sort_order = db.Column(db.Integer, default=0, index=True)
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)


class WhatsappBotKnowledge(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    source_url = db.Column(db.String(500), index=True)
    title = db.Column(db.String(255))
    content = db.Column(db.Text, nullable=False)
    keywords = db.Column(db.String(500))
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)


class WhatsappMessageLog(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    wa_id = db.Column(db.String(80), index=True)
    customer_name = db.Column(db.String(180))
    incoming_text = db.Column(db.Text)
    outgoing_text = db.Column(db.Text)
    provider_message_id = db.Column(db.String(120))
    status = db.Column(db.String(40), default='received', index=True)
    raw_payload = db.Column(db.Text)
    error = db.Column(db.Text)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)

class AiKnowledgeItem(TimestampMixin, db.Model):
    __tablename__ = 'ai_knowledge_item'
    id = db.Column(db.Integer, primary_key=True)
    source_type = db.Column(db.String(60), default='manual', index=True)
    source_ref = db.Column(db.String(500), index=True)
    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    keywords = db.Column(db.String(500))
    is_active = db.Column(db.Boolean, default=True, index=True)
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)
    last_synced_at = db.Column(db.DateTime)


class AiChatLog(TimestampMixin, db.Model):
    __tablename__ = 'ai_chat_log'
    id = db.Column(db.Integer, primary_key=True)
    session_id = db.Column(db.String(120), index=True)
    customer_name = db.Column(db.String(180))
    customer_email = db.Column(db.String(180))
    incoming_text = db.Column(db.Text)
    outgoing_text = db.Column(db.Text)
    intent = db.Column(db.String(80), index=True)
    handoff_status = db.Column(db.String(40), default='auto', index=True)
    context_snapshot = db.Column(db.Text)
    ip_address = db.Column(db.String(80))
    institution_id = db.Column(db.Integer, db.ForeignKey('institution.id'), index=True)
