from app import db, login_manager
from flask_login import UserMixin
from datetime import datetime
import bcrypt
import json


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


class User(UserMixin, db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    nama = db.Column(db.String(100), nullable=False)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    role = db.Column(db.Enum('admin', 'operator'), default='operator', nullable=False)
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    def set_password(self, password):
        self.password_hash = bcrypt.hashpw(
            password.encode('utf-8'), bcrypt.gensalt()
        ).decode('utf-8')

    def check_password(self, password):
        return bcrypt.checkpw(
            password.encode('utf-8'), self.password_hash.encode('utf-8')
        )

    def __repr__(self):
        return f'<User {self.username}>'


class Penduduk(db.Model):
    __tablename__ = 'penduduk'

    id = db.Column(db.Integer, primary_key=True)
    # Identitas
    nama = db.Column(db.String(150), nullable=False)
    jenis_kelamin = db.Column(db.Enum('L', 'P'), nullable=False)
    kebangsaan = db.Column(db.String(50), default='WNI')
    tempat_lahir = db.Column(db.String(100))
    tanggal_lahir = db.Column(db.Date)
    agama = db.Column(db.Enum('Islam', 'Kristen', 'Katholik', 'Hindu', 'Buddha', 'Konghucu', 'Lainnya'))
    pendidikan = db.Column(db.Enum(
        'Tidak Sekolah', 'SD', 'SMP', 'SMA/SMK', 'D1', 'D2', 'D3', 'S1', 'S2', 'S3'
    ))
    # Alamat
    rt = db.Column(db.String(5))
    rw = db.Column(db.String(5))
    kelurahan = db.Column(db.String(100))
    kecamatan = db.Column(db.String(100))
    kota = db.Column(db.String(100))
    provinsi = db.Column(db.String(100))
    kodepos = db.Column(db.String(10))
    # Sosial-Ekonomi
    status_rumah = db.Column(db.Enum('Milik Sendiri', 'Sewa/Kontrak', 'Menumpang', 'Dinas', 'Lainnya'))
    status_pekerjaan = db.Column(db.Enum(
        'Bekerja', 'Tidak Bekerja', 'Pelajar/Mahasiswa', 'Ibu Rumah Tangga', 'Pensiunan'
    ))
    bidang_usaha = db.Column(db.String(100))
    jenis_disabilitas = db.Column(db.Enum(
        'Tidak Ada', 'Fisik', 'Mental', 'Intelektual', 'Sensorik', 'Ganda'
    ), default='Tidak Ada')
    jenis_pkd = db.Column(db.String(100))  # Program Kesejahteraan Daerah
    # Metadata
    tahap = db.Column(db.String(50), nullable=False)  # e.g. "Tahap 1 2026"
    tahun_penerima = db.Column(db.Integer)
    created_by = db.Column(db.Integer, db.ForeignKey('users.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    creator = db.relationship('User', backref='penduduk_entries')

    def __repr__(self):
        return f'<Penduduk {self.nama}>'


class ClusteringSession(db.Model):
    __tablename__ = 'clustering_sessions'

    id = db.Column(db.Integer, primary_key=True)
    nama_sesi = db.Column(db.String(200), nullable=False)
    tahap = db.Column(db.String(50), nullable=False)
    metode = db.Column(db.Enum('kmeans', 'hierarchical'), nullable=False)
    normalisasi = db.Column(db.Enum('zscore', 'minmax'), nullable=False)
    n_cluster = db.Column(db.Integer, nullable=False)
    # Hierarchical specific
    linkage_method = db.Column(db.String(20))  # ward, complete, average, single
    # Results
    silhouette_score = db.Column(db.Float)
    jumlah_data = db.Column(db.Integer)
    kesimpulan = db.Column(db.Text)
    rekomendasi = db.Column(db.Text)
    # Stored JSON data
    centroids_json = db.Column(db.Text)       # K-Means centroids
    dendrogram_img = db.Column(db.Text)       # base64 image for hierarchical
    fitur_json = db.Column(db.Text)           # features used
    map_data_json = db.Column(db.Text)        # geocoded RW markers untuk peta
    # Metadata
    created_by = db.Column(db.Integer, db.ForeignKey('users.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    creator = db.relationship('User', backref='clustering_sessions')
    results = db.relationship('ClusterResult', backref='session', cascade='all, delete-orphan')

    @property
    def centroids(self):
        if self.centroids_json:
            return json.loads(self.centroids_json)
        return None

    @centroids.setter
    def centroids(self, value):
        self.centroids_json = json.dumps(value) if value else None

    @property
    def fitur(self):
        if self.fitur_json:
            return json.loads(self.fitur_json)
        return []

    @fitur.setter
    def fitur(self, value):
        self.fitur_json = json.dumps(value) if value else None

    @property
    def map_data(self):
        if self.map_data_json:
            return json.loads(self.map_data_json)
        return None

    @map_data.setter
    def map_data(self, value):
        self.map_data_json = json.dumps(value) if value else None

    def __repr__(self):
        return f'<ClusteringSession {self.nama_sesi}>'


class ClusterResult(db.Model):
    __tablename__ = 'cluster_results'

    id = db.Column(db.Integer, primary_key=True)
    session_id = db.Column(db.Integer, db.ForeignKey('clustering_sessions.id'), nullable=False)
    penduduk_id = db.Column(db.Integer, db.ForeignKey('penduduk.id'), nullable=False)
    cluster_label = db.Column(db.Integer, nullable=False)
    prioritas = db.Column(db.Enum('Prioritas Tinggi', 'Prioritas Sedang', 'Prioritas Rendah'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    penduduk = db.relationship('Penduduk', backref='cluster_results')

    def __repr__(self):
        return f'<ClusterResult session={self.session_id} cluster={self.cluster_label}>'


class RekomendasiSession(db.Model):
    __tablename__ = 'rekomendasi_sessions'

    id = db.Column(db.Integer, primary_key=True)
    nama_sesi = db.Column(db.String(200), nullable=False)
    clustering_session_id = db.Column(
        db.Integer,
        db.ForeignKey('clustering_sessions.id', ondelete='CASCADE'),
        nullable=False
    )
    ringkasan = db.Column(db.Text)
    created_by = db.Column(db.Integer, db.ForeignKey('users.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    clustering_session = db.relationship(
        'ClusteringSession',
        backref=db.backref('rekomendasi_sessions', cascade='all, delete-orphan')
    )
    creator = db.relationship('User', backref='rekomendasi_sessions')
    items = db.relationship(
        'RekomendasiItem',
        backref='rekomendasi_session',
        cascade='all, delete-orphan',
        order_by='RekomendasiItem.level, RekomendasiItem.label'
    )

    def __repr__(self):
        return f'<RekomendasiSession {self.nama_sesi}>'


class RekomendasiItem(db.Model):
    __tablename__ = 'rekomendasi_items'

    id = db.Column(db.Integer, primary_key=True)
    rekomendasi_session_id = db.Column(
        db.Integer,
        db.ForeignKey('rekomendasi_sessions.id', ondelete='CASCADE'),
        nullable=False
    )
    level = db.Column(db.Enum('cluster', 'rw'), nullable=False)
    label = db.Column(db.String(100), nullable=False)   # "Cluster 1" / "RW 005"
    prioritas = db.Column(db.String(50))
    kondisi_json = db.Column(db.Text)   # list kondisi terdeteksi + angka
    tindakan_json = db.Column(db.Text)  # list rekomendasi tindakan

    @property
    def kondisi(self):
        return json.loads(self.kondisi_json) if self.kondisi_json else []

    @kondisi.setter
    def kondisi(self, value):
        self.kondisi_json = json.dumps(value, ensure_ascii=False) if value else None

    @property
    def tindakan(self):
        return json.loads(self.tindakan_json) if self.tindakan_json else []

    @tindakan.setter
    def tindakan(self, value):
        self.tindakan_json = json.dumps(value, ensure_ascii=False) if value else None

    def __repr__(self):
        return f'<RekomendasiItem {self.level}:{self.label}>'