# ============================================================================
# Oderith Dental — Modelos de Base de Datos (SQLAlchemy)
# ============================================================================
# Todos los modelos del sistema con multi-tenancy por doctor.
# Cada tabla que contiene datos de pacientes tiene un doctor_id
# para garantizar el aislamiento de datos entre doctores.
#
# Modelos de administración:
#   - SuperAdmin: usuario global que gestiona clínicas y planes.
#   - Clinic: clínica dental con plan de suscripción.
# ============================================================================

from datetime import datetime, date, timedelta
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()


# ============================================================================
# HELPER: Obtener el doctor_id efectivo de cualquier usuario
# ============================================================================

def get_effective_doctor_id(user):
    """
    Retorna el doctor_id efectivo de un usuario autenticado.
    - Si es Doctor: retorna su propio id.
    - Si es Asistente: retorna el doctor_id al que está vinculado.
    - Si es SuperAdmin: retorna None (no tiene doctor_id).
    Útil para filtrar datos en todas las consultas multi-tenant.
    """
    if isinstance(user, Doctor):
        return user.id
    elif isinstance(user, Assistant):
        return user.doctor_id
    elif isinstance(user, SuperAdmin):
        return None
    else:
        raise ValueError('Tipo de usuario no reconocido')


# ============================================================================
# MODELO: Super Administrador
# ============================================================================

class SuperAdmin(UserMixin, db.Model):
    """
    Modelo de super administrador del sistema.
    Gestiona clínicas, planes de suscripción y usuarios globales.
    Solo puede existir un número reducido de superadmins.
    """
    __tablename__ = 'super_admins'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    name = db.Column(db.String(100))
    email = db.Column(db.String(150))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # --- Métodos de contraseña ---

    def set_password(self, password):
        """Genera y almacena el hash de la contraseña."""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Verifica una contraseña contra el hash almacenado."""
        return check_password_hash(self.password_hash, password)

    # --- Identificador para Flask-Login ---

    def get_id(self):
        """Retorna un identificador único con prefijo de rol para Flask-Login."""
        return f'superadmin:{self.id}'

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'username': self.username,
            'name': self.name,
            'email': self.email,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'role': 'superadmin',
        }

    def __repr__(self):
        return f'<SuperAdmin {self.id}: {self.username}>'


# ============================================================================
# MODELO: Clínica
# ============================================================================

class Clinic(db.Model):
    """
    Modelo de clínica dental con plan de suscripción.
    Cada clínica agrupa doctores y tiene un plan que controla el acceso.
    Los planes pueden ser: mensual, trimestral, semestral, anual o regalía.
    """
    __tablename__ = 'clinics'

    # Duraciones de cada tipo de plan en días
    PLAN_DURATIONS = {
        'mensual': 30,
        'trimestral': 90,
        'semestral': 180,
        'anual': 365,
    }

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(150), nullable=False)
    rif = db.Column(db.String(30))                           # RIF / NIT
    address = db.Column(db.Text)
    phone = db.Column(db.String(20))
    email = db.Column(db.String(150))
    city = db.Column(db.String(100))

    # --- Plan de suscripción ---
    plan_type = db.Column(db.String(20), nullable=False)      # mensual, trimestral, semestral, anual, regalia
    plan_start_date = db.Column(db.Date, nullable=False)
    plan_end_date = db.Column(db.Date, nullable=True)         # NULL si es regalía
    price_paid = db.Column(db.Numeric(10, 2))

    active = db.Column(db.Boolean, default=True)
    notes = db.Column(db.Text)                                # Observaciones del superadmin

    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    # Código único para que doctores se registren en esta clínica
    clinic_code = db.Column(db.String(20), unique=True)

    # --- Relaciones ---
    doctors = db.relationship('Doctor', backref='clinic', lazy='dynamic')

    # --- Métodos de estado del plan ---

    def is_expired(self):
        """
        Verifica si el plan de la clínica está vencido.
        Retorna False si es regalía (nunca vence).
        """
        if self.plan_type == 'regalia':
            return False
        if self.plan_end_date is None:
            return False
        return date.today() > self.plan_end_date

    def days_remaining(self):
        """
        Retorna los días restantes del plan.
        None si es regalía (no tiene vencimiento).
        """
        if self.plan_type == 'regalia':
            return None
        if self.plan_end_date is None:
            return None
        delta = (self.plan_end_date - date.today()).days
        return max(delta, 0)

    def status_label(self):
        """
        Retorna una etiqueta legible del estado del plan.
        - "Regalía": plan sin vencimiento.
        - "Vencida": plan expirado.
        - "Por vencer": le quedan 7 días o menos.
        - "Activa": plan vigente.
        """
        if self.plan_type == 'regalia':
            return 'Regalía'
        if self.is_expired():
            return 'Vencida'
        remaining = self.days_remaining()
        if remaining is not None and remaining <= 7:
            return 'Por vencer'
        return 'Activa'

    def renew(self, plan_type, price_paid=None):
        """
        Renueva el plan de la clínica desde hoy.
        Recalcula plan_end_date según el nuevo plan_type.
        """
        self.plan_type = plan_type
        self.plan_start_date = date.today()

        if plan_type == 'regalia':
            self.plan_end_date = None
        else:
            days = self.PLAN_DURATIONS.get(plan_type, 30)
            self.plan_end_date = date.today() + timedelta(days=days)

        if price_paid is not None:
            self.price_paid = price_paid

        self.active = True
        self.updated_at = datetime.utcnow()

    # --- Serialización ---

    def to_dict(self, include_stats=False):
        """Convierte el modelo a diccionario para respuestas JSON."""
        data = {
            'id': self.id,
            'name': self.name,
            'rif': self.rif,
            'address': self.address,
            'phone': self.phone,
            'email': self.email,
            'city': self.city,
            'plan_type': self.plan_type,
            'plan_start_date': self.plan_start_date.isoformat() if self.plan_start_date else None,
            'plan_end_date': self.plan_end_date.isoformat() if self.plan_end_date else None,
            'price_paid': float(self.price_paid) if self.price_paid else 0,
            'active': self.active,
            'notes': self.notes,
            'clinic_code': self.clinic_code,
            'status_label': self.status_label(),
            'days_remaining': self.days_remaining(),
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }
        if include_stats:
            data['total_doctors'] = self.doctors.count()
            # Contar pacientes de todos los doctores de esta clínica
            total_patients = 0
            for doc in self.doctors.all():
                total_patients += doc.patients.count()
            data['total_patients'] = total_patients
        return data

    def __repr__(self):
        return f'<Clinic {self.id}: {self.name} ({self.plan_type})>'


# ============================================================================
# MODELO: Doctor
# ============================================================================

class Doctor(UserMixin, db.Model):
    """
    Modelo principal de doctor/odontólogo.
    Es el 'tenant' del sistema — todos los datos están aislados por doctor_id.
    Ahora pertenece a una Clinic mediante clinic_id.
    """
    __tablename__ = 'doctors'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(150), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    phone = db.Column(db.String(20))
    specialty = db.Column(db.String(100))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    active = db.Column(db.Boolean, default=True)

    # --- Relación con Clínica ---
    clinic_id = db.Column(db.Integer, db.ForeignKey('clinics.id'), nullable=True)

    # --- Relaciones ---
    assistants = db.relationship('Assistant', backref='doctor', lazy='dynamic',
                                 cascade='all, delete-orphan')
    patients = db.relationship('Patient', backref='doctor', lazy='dynamic',
                               cascade='all, delete-orphan')
    appointments = db.relationship('Appointment', backref='doctor', lazy='dynamic',
                                   cascade='all, delete-orphan')
    budgets = db.relationship('Budget', backref='doctor', lazy='dynamic',
                              cascade='all, delete-orphan')
    accounts = db.relationship('Account', backref='doctor', lazy='dynamic',
                               cascade='all, delete-orphan')
    odontograms = db.relationship('Odontogram', backref='doctor', lazy='dynamic',
                                  cascade='all, delete-orphan')
    whatsapp_logs = db.relationship('WhatsappLog', backref='doctor', lazy='dynamic',
                                    cascade='all, delete-orphan')

    # --- Métodos de contraseña ---

    def set_password(self, password):
        """Genera y almacena el hash de la contraseña."""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Verifica una contraseña contra el hash almacenado."""
        return check_password_hash(self.password_hash, password)

    # --- Identificador para Flask-Login ---

    def get_id(self):
        """Retorna un identificador único con prefijo de rol para Flask-Login."""
        return f'doctor:{self.id}'

    # --- Serialización ---

    def to_dict(self, include_stats=False):
        """Convierte el modelo a diccionario para respuestas JSON."""
        data = {
            'id': self.id,
            'name': self.name,
            'email': self.email,
            'phone': self.phone,
            'specialty': self.specialty,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'active': self.active,
            'role': 'doctor',
            'clinic_id': self.clinic_id,
            'clinic_name': self.clinic.name if self.clinic else None,
        }
        if include_stats:
            data['total_patients'] = self.patients.count()
            data['total_assistants'] = self.assistants.count()
        return data

    def __repr__(self):
        return f'<Doctor {self.id}: {self.name}>'


# ============================================================================
# MODELO: Asistente
# ============================================================================

class Assistant(UserMixin, db.Model):
    """
    Modelo de asistente vinculado a un doctor.
    Solo puede ver y gestionar los datos del doctor al que pertenece.
    Los permisos controlan qué módulos puede acceder.
    """
    __tablename__ = 'assistants'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(150), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    phone = db.Column(db.String(20))
    permissions = db.Column(db.JSON, default=list)
    # Permisos válidos: pacientes, citas, presupuestos, cobros, whatsapp, odontograma
    active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # Lista de permisos válidos del sistema
    VALID_PERMISSIONS = [
        'pacientes', 'citas', 'presupuestos',
        'cobros', 'whatsapp', 'odontograma'
    ]

    # --- Métodos de contraseña ---

    def set_password(self, password):
        """Genera y almacena el hash de la contraseña."""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Verifica una contraseña contra el hash almacenado."""
        return check_password_hash(self.password_hash, password)

    # --- Métodos de permisos ---

    def has_permission(self, permission):
        """Verifica si el asistente tiene un permiso específico."""
        return permission in (self.permissions or [])

    def set_permissions(self, permissions_list):
        """Establece la lista de permisos, validando cada uno."""
        self.permissions = [
            p for p in permissions_list if p in self.VALID_PERMISSIONS
        ]

    # --- Identificador para Flask-Login ---

    def get_id(self):
        """Retorna un identificador único con prefijo de rol para Flask-Login."""
        return f'assistant:{self.id}'

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'doctor_name': self.doctor.name if self.doctor else None,
            'name': self.name,
            'email': self.email,
            'phone': self.phone,
            'permissions': self.permissions or [],
            'active': self.active,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'role': 'assistant',
        }

    def __repr__(self):
        return f'<Assistant {self.id}: {self.name} → Doctor {self.doctor_id}>'


# ============================================================================
# MODELO: Paciente
# ============================================================================

class Patient(db.Model):
    """
    Modelo de paciente, aislado por doctor_id (multi-tenancy).
    La cédula es única dentro del contexto de cada doctor.
    """
    __tablename__ = 'patients'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    name = db.Column(db.String(100), nullable=False)
    cedula = db.Column(db.String(20), nullable=False)
    birth_date = db.Column(db.Date)
    gender = db.Column(db.String(10))
    phone = db.Column(db.String(20))
    email = db.Column(db.String(150))
    address = db.Column(db.Text)
    medical_conditions = db.Column(db.JSON, default=list)
    # Condiciones válidas: diabetes, hipertension, cardiopatias, alergias,
    #                      hepatitis, vih, asma, epilepsia, embarazo, otros
    medications = db.Column(db.Text)
    allergies_detail = db.Column(db.Text)
    observations = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    # Índice compuesto: cédula única por doctor
    __table_args__ = (
        db.UniqueConstraint('doctor_id', 'cedula', name='uq_doctor_cedula'),
    )

    # --- Relaciones ---
    appointments = db.relationship('Appointment', backref='patient', lazy='dynamic',
                                   cascade='all, delete-orphan')
    budgets = db.relationship('Budget', backref='patient', lazy='dynamic',
                              cascade='all, delete-orphan')
    accounts = db.relationship('Account', backref='patient', lazy='dynamic',
                               cascade='all, delete-orphan')
    odontograms = db.relationship('Odontogram', backref='patient', lazy='dynamic',
                                  cascade='all, delete-orphan')
    whatsapp_logs = db.relationship('WhatsappLog', backref='patient', lazy='dynamic')

    # --- Serialización ---

    def to_dict(self, include_history=False):
        """Convierte el modelo a diccionario para respuestas JSON."""
        data = {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'name': self.name,
            'cedula': self.cedula,
            'birth_date': self.birth_date.isoformat() if self.birth_date else None,
            'gender': self.gender,
            'phone': self.phone,
            'email': self.email,
            'address': self.address,
            'medical_conditions': self.medical_conditions or [],
            'medications': self.medications,
            'allergies_detail': self.allergies_detail,
            'observations': self.observations,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }
        if include_history:
            data['total_appointments'] = self.appointments.count()
            data['total_budgets'] = self.budgets.count()
        return data

    def __repr__(self):
        return f'<Patient {self.id}: {self.name} ({self.cedula})>'




# ============================================================================
# MODELO: Radiografías / Imágenes del Paciente
# ============================================================================

class PatientImage(db.Model):
    """
    Almacena radiografías e imágenes clínicas del paciente.
    Cada imagen tiene una nota para seguimiento de evolución.
    """
    __tablename__ = 'patient_images'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id', ondelete='CASCADE'), nullable=False)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    filename = db.Column(db.String(255), nullable=False)
    original_name = db.Column(db.String(255))
    file_type = db.Column(db.String(50))  # image/jpeg, image/png, etc.
    file_size = db.Column(db.Integer)  # bytes
    category = db.Column(db.String(20), default='rx')  # 'rx' = Radiografía, 'foto' = Fotografía clínica
    title = db.Column(db.String(200))  # Ej: "Radiografía panorámica", "Rx periapical 47"
    notes = db.Column(db.Text)  # Nota de evolución
    image_date = db.Column(db.Date)  # Fecha de la radiografía
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # --- Relación ---
    patient = db.relationship('Patient', backref=db.backref('images', lazy='dynamic',
                              cascade='all, delete-orphan'))

    def to_dict(self):
        return {
            'id': self.id,
            'patient_id': self.patient_id,
            'category': self.category or 'rx',
            'filename': self.filename,
            'original_name': self.original_name,
            'file_type': self.file_type,
            'file_size': self.file_size,
            'title': self.title,
            'notes': self.notes,
            'image_date': self.image_date.isoformat() if self.image_date else None,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'url': f'/uploads/{self.filename}' if self.filename else None,
        }

    def __repr__(self):
        return f'<PatientImage {self.id}: {self.title}>'

# ============================================================================
# MODELO: Cita
# ============================================================================

class Appointment(db.Model):
    """
    Modelo de cita/consulta odontológica.
    Vinculada a un doctor y un paciente.
    """
    __tablename__ = 'appointments'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id'), nullable=False)
    date = db.Column(db.Date, nullable=False)
    start_time = db.Column(db.Time, nullable=False)
    end_time = db.Column(db.Time)
    reason = db.Column(db.Text)
    status = db.Column(db.String(20), default='Programada')
    # Estados válidos: Programada, Confirmada, En curso, Completada, Cancelada
    notes = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # --- Relaciones ---
    whatsapp_logs = db.relationship('WhatsappLog', backref='appointment', lazy='dynamic')

    VALID_STATUSES = [
        'Programada', 'Confirmada', 'En curso', 'Completada', 'Cancelada'
    ]

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'patient_id': self.patient_id,
            'patient_name': self.patient.name if self.patient else None,
            'date': self.date.isoformat() if self.date else None,
            'start_time': self.start_time.isoformat() if self.start_time else None,
            'end_time': self.end_time.isoformat() if self.end_time else None,
            'reason': self.reason,
            'status': self.status,
            'notes': self.notes,
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }

    def __repr__(self):
        return f'<Appointment {self.id}: {self.date} {self.start_time}>'


# ============================================================================
# MODELO: Presupuesto
# ============================================================================

class Budget(db.Model):
    """
    Modelo de presupuesto odontológico.
    Contiene ítems detallados con precios y descuentos.
    """
    __tablename__ = 'budgets'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id'), nullable=False)
    subtotal = db.Column(db.Numeric(10, 2), default=0)
    discount_type = db.Column(db.String(10))  # 'percent' o 'fixed'
    discount_value = db.Column(db.Numeric(10, 2), default=0)
    discount_amount = db.Column(db.Numeric(10, 2), default=0)
    total = db.Column(db.Numeric(10, 2), default=0)
    status = db.Column(db.String(20), default='Pendiente')
    # Estados válidos: Pendiente, Aprobado, En progreso, Completado, Rechazado
    notes = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    # --- Relaciones ---
    items = db.relationship('BudgetItem', backref='budget', lazy='dynamic',
                            cascade='all, delete-orphan')
    accounts = db.relationship('Account', backref='budget', lazy='dynamic')

    VALID_STATUSES = [
        'Pendiente', 'Aprobado', 'En progreso', 'Completado', 'Rechazado'
    ]

    # --- Métodos de cálculo ---

    def recalculate(self):
        """Recalcula subtotal, descuento y total basado en los ítems."""
        self.subtotal = sum(
            item.subtotal or 0 for item in self.items.all()
        )
        if self.discount_type == 'percent' and self.discount_value:
            self.discount_amount = round(
                float(self.subtotal) * float(self.discount_value) / 100, 2
            )
        elif self.discount_type == 'fixed' and self.discount_value:
            self.discount_amount = float(self.discount_value)
        else:
            self.discount_amount = 0
        self.total = float(self.subtotal) - float(self.discount_amount)

    # --- Serialización ---

    def to_dict(self, include_items=False):
        """Convierte el modelo a diccionario para respuestas JSON."""
        data = {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'patient_id': self.patient_id,
            'patient_name': self.patient.name if self.patient else None,
            'subtotal': float(self.subtotal) if self.subtotal else 0,
            'discount_type': self.discount_type,
            'discount_value': float(self.discount_value) if self.discount_value else 0,
            'discount_amount': float(self.discount_amount) if self.discount_amount else 0,
            'total': float(self.total) if self.total else 0,
            'status': self.status,
            'notes': self.notes,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }
        if include_items:
            data['items'] = [item.to_dict() for item in self.items.all()]
        return data

    def __repr__(self):
        return f'<Budget {self.id}: ${self.total}>'


# ============================================================================
# MODELO: Ítem de Presupuesto
# ============================================================================

class BudgetItem(db.Model):
    """
    Línea individual dentro de un presupuesto.
    Representa un tratamiento o procedimiento con su precio.
    """
    __tablename__ = 'budget_items'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    budget_id = db.Column(db.Integer, db.ForeignKey('budgets.id', ondelete='CASCADE'),
                          nullable=False)
    description = db.Column(db.String(255), nullable=False)
    teeth = db.Column(db.String(50))  # Dientes afectados (ej: "11,12,21")
    quantity = db.Column(db.Integer, default=1)
    unit_price = db.Column(db.Numeric(10, 2), nullable=False)
    subtotal = db.Column(db.Numeric(10, 2))

    # --- Métodos de cálculo ---

    def calculate_subtotal(self):
        """Calcula el subtotal del ítem: cantidad × precio unitario."""
        self.subtotal = (self.quantity or 1) * float(self.unit_price or 0)

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'budget_id': self.budget_id,
            'description': self.description,
            'teeth': self.teeth,
            'quantity': self.quantity,
            'unit_price': float(self.unit_price) if self.unit_price else 0,
            'subtotal': float(self.subtotal) if self.subtotal else 0,
        }

    def __repr__(self):
        return f'<BudgetItem {self.id}: {self.description}>'


# ============================================================================
# MODELO: Cuenta por Cobrar
# ============================================================================

class Account(db.Model):
    """
    Cuenta por cobrar vinculada a un paciente y opcionalmente a un presupuesto.
    Registra el monto total, lo pagado y el saldo pendiente.
    """
    __tablename__ = 'accounts'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id'), nullable=False)
    budget_id = db.Column(db.Integer, db.ForeignKey('budgets.id'), nullable=True)
    total_amount = db.Column(db.Numeric(10, 2), nullable=False)
    paid_amount = db.Column(db.Numeric(10, 2), default=0)
    balance = db.Column(db.Numeric(10, 2))  # Calculado: total - pagado
    status = db.Column(db.String(20), default='Pendiente')
    # Estados válidos: Pendiente, Pagado
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # --- Relaciones ---
    payments = db.relationship('Payment', backref='account', lazy='dynamic',
                               cascade='all, delete-orphan')

    # --- Métodos de cálculo ---

    def recalculate_balance(self):
        """Recalcula el saldo basado en los pagos realizados."""
        self.paid_amount = sum(
            float(p.amount) for p in self.payments.all()
        )
        self.balance = float(self.total_amount) - float(self.paid_amount)
        self.status = 'Pagado' if self.balance <= 0 else 'Pendiente'

    # --- Serialización ---

    def to_dict(self, include_payments=False):
        """Convierte el modelo a diccionario para respuestas JSON."""
        data = {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'patient_id': self.patient_id,
            'patient_name': self.patient.name if self.patient else None,
            'budget_id': self.budget_id,
            'total_amount': float(self.total_amount) if self.total_amount else 0,
            'paid_amount': float(self.paid_amount) if self.paid_amount else 0,
            'balance': float(self.balance) if self.balance is not None else 0,
            'status': self.status,
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }
        if include_payments:
            data['payments'] = [p.to_dict() for p in self.payments.all()]
        return data

    def __repr__(self):
        return f'<Account {self.id}: Total ${self.total_amount}, Balance ${self.balance}>'


# ============================================================================
# MODELO: Pago
# ============================================================================

class Payment(db.Model):
    """
    Registro individual de pago asociado a una cuenta por cobrar.
    """
    __tablename__ = 'payments'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    account_id = db.Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False)
    amount = db.Column(db.Numeric(10, 2), nullable=False)
    method = db.Column(db.String(30))
    # Métodos válidos: Efectivo, Transferencia, Tarjeta, Otro
    reference = db.Column(db.String(100))
    notes = db.Column(db.Text)
    payment_date = db.Column(db.DateTime, default=datetime.utcnow)

    VALID_METHODS = ['Efectivo', 'Transferencia', 'Tarjeta', 'Otro']

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'account_id': self.account_id,
            'amount': float(self.amount) if self.amount else 0,
            'method': self.method,
            'reference': self.reference,
            'notes': self.notes,
            'payment_date': self.payment_date.isoformat() if self.payment_date else None,
        }

    def __repr__(self):
        return f'<Payment {self.id}: ${self.amount} ({self.method})>'


# ============================================================================
# MODELO: Odontograma
# ============================================================================

class Odontogram(db.Model):
    """
    Odontograma digital del paciente.
    Almacena el estado de cada diente y sus caras en formato JSON.
    
    Estructura del campo 'data':
    {
        "teeth": {
            "11": {"O": "caries", "V": "sano", "M": "restauracion", "D": "sano", "P": "sano"},
            "12": {"O": "sano", ...},
            ...
        }
    }
    
    Caras: O=Oclusal, V=Vestibular, M=Mesial, D=Distal, P=Palatino/Lingual
    Estados: sano, caries, restauracion, corona, extraccion, endodoncia, sellante, fractura
    """
    __tablename__ = 'odontograms'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id'), nullable=False)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    data = db.Column(db.JSON, default=dict)
    notes = db.Column(db.Text)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    # Un odontograma por paciente por doctor
    __table_args__ = (
        db.UniqueConstraint('patient_id', 'doctor_id', name='uq_patient_doctor_odontogram'),
    )

    # --- Métodos de actualización ---

    def update_tooth(self, tooth_number, face, condition):
        """
        Actualiza el estado de una cara de un diente específico.
        
        Args:
            tooth_number: Número del diente (ej: "11", "48")
            face: Cara del diente (O, V, M, D, P)
            condition: Estado (sano, caries, restauracion, etc.)
        """
        if not self.data:
            self.data = {'teeth': {}}
        if 'teeth' not in self.data:
            self.data['teeth'] = {}
        if tooth_number not in self.data['teeth']:
            self.data['teeth'][tooth_number] = {}
        self.data['teeth'][tooth_number][face] = condition

    def get_tooth(self, tooth_number):
        """Retorna el estado de todas las caras de un diente."""
        if not self.data or 'teeth' not in self.data:
            return {}
        return self.data.get('teeth', {}).get(tooth_number, {})

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'patient_id': self.patient_id,
            'patient_name': self.patient.name if self.patient else None,
            'doctor_id': self.doctor_id,
            'data': self.data or {'teeth': {}},
            'notes': self.notes,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None,
        }

    def __repr__(self):
        return f'<Odontogram {self.id}: Patient {self.patient_id}>'


# ============================================================================
# MODELO: Registro de WhatsApp
# ============================================================================

class WhatsappLog(db.Model):
    """
    Registro de mensajes de WhatsApp enviados a pacientes.
    Para auditoría y seguimiento de comunicaciones.
    """
    __tablename__ = 'whatsapp_logs'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    doctor_id = db.Column(db.Integer, db.ForeignKey('doctors.id'), nullable=False)
    patient_id = db.Column(db.Integer, db.ForeignKey('patients.id'), nullable=True)
    appointment_id = db.Column(db.Integer, db.ForeignKey('appointments.id'), nullable=True)
    message = db.Column(db.Text)
    phone = db.Column(db.String(20))
    sent_at = db.Column(db.DateTime, default=datetime.utcnow)

    # --- Serialización ---

    def to_dict(self):
        """Convierte el modelo a diccionario para respuestas JSON."""
        return {
            'id': self.id,
            'doctor_id': self.doctor_id,
            'patient_id': self.patient_id,
            'patient_name': self.patient.name if self.patient else None,
            'appointment_id': self.appointment_id,
            'message': self.message,
            'phone': self.phone,
            'sent_at': self.sent_at.isoformat() if self.sent_at else None,
        }

    def __repr__(self):
        return f'<WhatsappLog {self.id}: → {self.phone}>'
