from flask_login import UserMixin, current_user
from sqlalchemy import select

from web_application import cipher_suite, db, login_manager
from web_application.constants import AI_TOOLS, chars_dict


@login_manager.user_loader
def load_user(user_id):
    smtp = select(User).where(User.id == user_id)
    user = db.session.execute(smtp).scalars().first()
    if user is None:
        return None
    return user


def generate_unique_username(full_name):
    """
    Bu fonksiyon ad soyad bilgisini alır, tüm karekterleri İngilizce karakterlere dönüştürür
    ve küçük harfe çevirir. Aynı kullanıcı adı mevcutsa sonuna bir sayı ekleyerek benzersiz hale getirir.
    """
    base_username = ""
    for char in full_name:
        if char in chars_dict.keys():
            base_username += chars_dict[char]
        else:
            base_username += char

    username = base_username.lower()

    stmt = select(User).where(User.username == username)
    users = db.session.execute(stmt).scalars().all()
    if users is None:
        return username
    else:
        return f"{username}_{len(users)}"


# Base User class
class User(UserMixin, db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    uuid = db.Column(db.String(36), unique=True, nullable=False)
    full_name = db.Column(db.String(50), unique=True, nullable=False)
    username = db.Column(db.String(50), unique=True, nullable=False)
    phone = db.Column(db.String(50), unique=True, nullable=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    role = db.Column(db.Integer, nullable=False, default=0)
    status = db.Column(db.Integer, nullable=False, default=2)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if "username" not in kwargs or not kwargs["username"]:
            self.username = generate_unique_username(self.full_name)

    @property
    def reset_token(self):
        smtp = select(PasswordResetToken).where(PasswordResetToken.user_id == self.id)
        return db.session.execute(smtp).scalars().first() or None

    @property
    def assessment_ids(self):
        stmt = select(DrAssessment).where(DrAssessment.user_id == self.id)
        assessments = db.session.execute(stmt).scalars().all()
        p_ids = [assessment.patient_id for assessment in assessments]
        return p_ids

    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )

    def get_role(self):
        smtp = select(Role).where(Role.id == self.role)
        role = db.session.execute(smtp).scalars().first()
        return role.name

    def get_api_key(self):
        smtp = select(APIKey).where(APIKey.user_id == self.id)
        return db.session.execute(smtp).scalars().first() or None

    def __repr__(self):
        return f"User({self.email})"


class Role(db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)

    def __repr__(self):
        return f"Role({self.name})"


class PasswordResetToken(db.Model):
    __tablename__ = "password_reset_tokens"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=False)
    token = db.Column(db.String(64), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

    @property
    def user(self):
        smtp = select(User).where(User.id == self.user_id)
        return db.session.execute(smtp).scalars().first()

    def __repr__(self):
        return f"PasswordResetToken({self.token})"


class APIKey(db.Model):
    __tablename__ = "api_keys"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, unique=True, nullable=False, default=0)
    # This column stores the ENCRYPTED API key (Fernet token)
    _encrypted_api_key = db.Column(db.Text, nullable=False, default="-")
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )

    @property
    def api_key(self):
        """Getter: Decrypts the key when retrieved from the model."""
        if self._encrypted_api_key:
            decrypted_bytes = cipher_suite.decrypt(self._encrypted_api_key.encode())
            return decrypted_bytes.decode()
        return None

    @api_key.setter
    def api_key(self, plain_key):
        """Setter: Encrypts the key before saving to the database."""
        if plain_key:
            encrypted_bytes = cipher_suite.encrypt(plain_key.encode())
            self._encrypted_api_key = encrypted_bytes.decode()
        else:
            self._encrypted_api_key = None


class Patient(db.Model):
    __tablename__ = "patients"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=False, default=0)
    case_type = db.Column(db.String(20), nullable=False, default="-")
    case_dir = db.Column(db.String(20), nullable=False, default="-")
    #
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )
    status = db.Column(
        db.Integer, nullable=False, default=1
    )  # 1: Pending, 2: AI Prediction, 3: Dr Assessment

    def ai_assessment(self, ai_name):
        smtp = select(AIAssessment).where(
            AIAssessment.patient_id == self.id, AIAssessment.ai_name == ai_name
        )
        res = db.session.execute(smtp).scalars().first() or None
        return res

    @property
    def ai_assessments(self):
        assessments = {}
        for ai_name in AI_TOOLS:
            # print(ai_name)
            smtp = select(AIAssessment).where(
                AIAssessment.patient_id == self.id, AIAssessment.ai_name == ai_name
            )
            res = db.session.execute(smtp).scalars().first()
            if res is not None:
                assessments[ai_name] = res

        return assessments

    @property
    def assessor_ids(self):
        smtp = select(DrAssessment).where(
            DrAssessment.patient_id == self.id, DrAssessment.user_id == current_user.id
        )
        assessors = db.session.execute(smtp).scalars().all()
        assessor_ids_list = [res.user_id for res in assessors]
        return assessor_ids_list

    @property
    def dr_assessment(self):
        smtp = select(DrAssessment).where(
            DrAssessment.patient_id == self.id, DrAssessment.user_id == current_user.id
        )
        return db.session.execute(smtp).scalars().first() or None

    def get_user(self):
        smtp = select(User).where(User.id == self.user_id)
        return db.session.execute(smtp).scalars().first()

    def get_images(self):
        smtp = select(Image).where(Image.patient_id == self.id)
        return db.session.execute(smtp).scalars().all()

    def __repr__(self):
        return "Diagnosis"


class Image(db.Model):
    __tablename__ = "images"
    id = db.Column(db.Integer, primary_key=True)
    patient_id = db.Column(db.Integer, nullable=False, default=0)
    filename = db.Column(db.String(250), unique=False, nullable=True, default="-")

    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )

    def get_diagnosis(self):
        smtp = select(Patient).where(Patient.id == self.patient_id)
        return db.session.execute(smtp).scalars().first()

    def __repr__(self):
        return "Image"


class AIAssessment(db.Model):
    __tablename__ = "ai_assessments"
    id = db.Column(db.Integer, primary_key=True)
    patient_id = db.Column(db.Integer, nullable=False, default=0)
    ai_name = db.Column(db.String(50), nullable=False, default="-")
    pnx_exists = db.Column(db.Integer, nullable=False, default=0)
    pnx_loc = db.Column(db.Text(), nullable=False, default="-")
    hmx_exists = db.Column(db.Integer, nullable=False, default=0)
    hmx_loc = db.Column(db.Text(), nullable=False, default="-")
    akk_exists = db.Column(db.Integer, nullable=False, default=0)
    akk_loc = db.Column(db.Text(), nullable=False, default="-")
    kot_exists = db.Column(db.Integer, nullable=False, default=0)
    kot_loc = db.Column(db.Text(), nullable=False, default="-")

    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )

    @property
    def patient(self):
        smtp = select(Patient).where(Patient.id == self.patient_id)
        return db.session.execute(smtp).scalars().first() or None

    def __repr__(self):
        return "AIAssessment"


class DrAssessment(db.Model):
    __tablename__ = "dr_assessments"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=False, default=0)
    patient_id = db.Column(db.Integer, nullable=False, default=0)
    gemini_pnx = db.Column(db.Integer, nullable=True, default=0)
    gemini_hmx = db.Column(db.Integer, nullable=True, default=0)
    gemini_akk = db.Column(db.Integer, nullable=True, default=0)
    gemini_kot = db.Column(db.Integer, nullable=True, default=0)
    openai_pnx = db.Column(db.Integer, nullable=True, default=0)
    openai_hmx = db.Column(db.Integer, nullable=True, default=0)
    openai_akk = db.Column(db.Integer, nullable=True, default=0)
    openai_kot = db.Column(db.Integer, nullable=True, default=0)
    claude_pnx = db.Column(db.Integer, nullable=True, default=0)
    claude_hmx = db.Column(db.Integer, nullable=True, default=0)
    claude_akk = db.Column(db.Integer, nullable=True, default=0)
    claude_kot = db.Column(db.Integer, nullable=True, default=0)
    perplexity_pnx = db.Column(db.Integer, nullable=True, default=0)
    perplexity_hmx = db.Column(db.Integer, nullable=True, default=0)
    perplexity_akk = db.Column(db.Integer, nullable=True, default=0)
    perplexity_kot = db.Column(db.Integer, nullable=True, default=0)
    grok_pnx = db.Column(db.Integer, nullable=True, default=0)
    grok_hmx = db.Column(db.Integer, nullable=True, default=0)
    grok_akk = db.Column(db.Integer, nullable=True, default=0)
    grok_kot = db.Column(db.Integer, nullable=True, default=0)

    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    updated_at = db.Column(
        db.DateTime,
        default=db.func.current_timestamp(),
        onupdate=db.func.current_timestamp(),
    )

    @property
    def patient(self):
        smtp = select(Patient).where(Patient.id == self.patient_id)
        return db.session.execute(smtp).scalars().first() or None

    def __repr__(self):
        return "DrAssessment"
