import enum
import json
import os
from datetime import datetime, timezone

from sqlalchemy import func, text
from sqlalchemy.types import Text, TypeDecorator

from backend.database.base import db

# TODO rename file in something like shared_models, not done for now to ease the future merge conflicts


class JsonEncoded(TypeDecorator):
    impl = Text
    cache_ok = True

    def process_bind_param(self, value, dialect):
        if value is None:
            return None
        return json.dumps(value)

    def process_result_value(self, value, dialect):
        if value is None:
            return None
        return json.loads(value)


def string_enum(enum_cls, **kwargs):
    return db.Enum(enum_cls, native_enum=False, values_callable=lambda obj: [e.value for e in obj], **kwargs)


class TimestampMixin:
    created_at = db.Column(db.DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
    last_modified = db.Column(
        db.DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc),
        nullable=False,
        server_default=func.now(),
    )


class PublishingStatusEnum(enum.Enum):
    IDLE = "idle"
    PUBLISHING = "publishing"
    PUBLISHED = "published"
    FAILED = "failed"


class StatusEnum(enum.Enum):
    ACTIVE = "active"
    DELETED = "deleted"


class PrincipalTypeEnum(enum.Enum):
    USER = "user"
    GROUP = "group"


# Get tables prefix (e.g., "agenthub_" -> "agenthub_agents")
tables_prefix = os.environ.get("TABLES_PREFIX", "") or ""

fk_prefix = ""  # needed for foreign key in case of schema usage
if os.environ.get("DB_SCHEMA", None):
    args = {"schema": os.environ["DB_SCHEMA"]}  # needed for PostgreSQL and other DB with schema support
    fk_prefix = f"{os.environ['DB_SCHEMA']}."
else:
    args = {}

# Add tables_prefix to fk_prefix for foreign key references
fk_prefix = f"{fk_prefix}{tables_prefix}"


class Agent(db.Model, TimestampMixin):
    __tablename__ = f"{tables_prefix}agents"
    __table_args__ = args

    id = db.Column(db.String, primary_key=True)
    owner = db.Column(db.String, nullable=False)
    description = db.Column(db.Text)
    sample_questions = db.Column(JsonEncoded, default=list)
    documents = db.Column(JsonEncoded, default=list)
    datasources = db.Column(JsonEncoded, default=list)
    indexing = db.Column(JsonEncoded)
    published_version = db.Column(JsonEncoded)
    published_at = db.Column(db.DateTime(timezone=True), nullable=True)
    publishing_status = db.Column(string_enum(PublishingStatusEnum), default=PublishingStatusEnum.IDLE)
    publishing_job_id = db.Column(db.String)
    publishing_scenario_runs = db.Column(JsonEncoded)

    def __repr__(self):
        return f"<Agent {self.id}>"


class Conversation(db.Model, TimestampMixin):
    __tablename__ = f"{tables_prefix}conversations"
    __table_args__ = args

    conversation_id = db.Column(db.String, primary_key=True)
    user_id = db.Column(db.String, nullable=False)
    title = db.Column(db.Text, default="")
    agent_ids = db.Column(JsonEncoded, default=list)
    llm_id = db.Column(db.String, default="")
    agents_enabled = db.Column(db.Boolean, default=True)
    doc_gen_enabled = db.Column(db.Boolean, default=True, nullable=False)
    status = db.Column(string_enum(StatusEnum), default=StatusEnum.ACTIVE)

    messages = db.relationship(
        "Message", backref="conversation", lazy=True, cascade="all, delete-orphan", order_by="Message.created_at"
    )


class Message(db.Model):
    __tablename__ = f"{tables_prefix}messages"
    __table_args__ = args

    id = db.Column(db.String, primary_key=True)
    conversation_id = db.Column(db.String, db.ForeignKey(f"{fk_prefix}conversations.conversation_id"), nullable=False)
    role = db.Column(db.String, nullable=False)
    content = db.Column(db.Text, nullable=False)
    event_log = db.Column(JsonEncoded, default=list)
    merged_context = db.Column(JsonEncoded, default=dict)
    created_at = db.Column(db.DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
    actions = db.Column(JsonEncoded, default=dict)
    artifacts = db.Column(db.LargeBinary)
    tool_validation_requests = db.Column(JsonEncoded, default=None, nullable=True)
    tool_validation_responses = db.Column(JsonEncoded, default=None, nullable=True)
    memory_fragment = db.Column(JsonEncoded, default=None, nullable=True)
    selected_agent_ids = db.Column(JsonEncoded, default=list)
    used_agent_ids = db.Column(JsonEncoded, default=list)
    llm_id = db.Column(db.String, default="")
    agents_enabled = db.Column(db.Boolean, default=True)
    feedback_rating = db.Column(db.Integer)
    feedback_text = db.Column(db.Text)
    feedback_by = db.Column(db.String)
    feedback_updated_at = db.Column(
        db.DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc),
    )
    trace = db.Column(db.LargeBinary)
    status = db.Column(string_enum(StatusEnum), default=StatusEnum.ACTIVE)


class MessageAgent(db.Model):
    __tablename__ = f"{tables_prefix}message_agents"
    __table_args__ = args

    message_id = db.Column(db.String, db.ForeignKey(f"{fk_prefix}messages.id", ondelete="CASCADE"), primary_key=True)
    agent_id = db.Column(db.String, primary_key=True)  # No FK - agents may be external/enterprise
    selected = db.Column(db.Integer, nullable=False)
    used = db.Column(db.Integer, nullable=False)


class Preference(db.Model, TimestampMixin):
    __tablename__ = f"{tables_prefix}preferences"
    __table_args__ = args

    user_id = db.Column(db.String, primary_key=True)
    prefs = db.Column(JsonEncoded, default=dict)


class MessageAttachment(db.Model, TimestampMixin):
    __tablename__ = f"{tables_prefix}message_attachments"
    __table_args__ = args

    message_id = db.Column(db.String, primary_key=True)
    attachments = db.Column(JsonEncoded, nullable=False, default=list)
    extraction_mode = db.Column(JsonEncoded, nullable=True)


class DerivedDocument(db.Model):
    __tablename__ = f"{tables_prefix}derived_documents"

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    conv_id = db.Column(db.String, nullable=False)
    user_id = db.Column(db.String, nullable=False)
    document_name = db.Column(db.String, nullable=False)
    document_path = db.Column(db.String, nullable=False)
    document_metadata = db.Column(JsonEncoded, nullable=False, default={"snapshots": []})
    created_at = db.Column(
        db.DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        nullable=False,
        server_default=func.now(),
    )

    __table_args__ = (db.UniqueConstraint("conv_id", "user_id", "document_path"), args)


class AdminSettings(db.Model):
    __tablename__ = f"{tables_prefix}admin_settings"
    __table_args__ = (db.UniqueConstraint("user", name="uq_admin_settings_user"), args)

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    created_at = db.Column(db.DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
    user = db.Column(db.String(255), nullable=False)
    version = db.Column(db.Integer, nullable=False, default=1, server_default=text("1"))
    settings = db.Column(JsonEncoded, nullable=False)

    def __repr__(self):
        return f"<AdminSettings {self.id} by {self.user}>"
