from datetime import datetime
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .database import Base


class Category(Base):
    __tablename__ = "categories"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100), unique=True, index=True)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    image_path: Mapped[str | None] = mapped_column(String(255), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

    assets: Mapped[list["Asset"]] = relationship(back_populates="category")
    visible_fields: Mapped[list["CategoryField"]] = relationship(
        back_populates="category", cascade="all, delete-orphan", order_by="CategoryField.sort_order"
    )


class FieldDefinition(Base):
    __tablename__ = "field_definitions"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    field_name: Mapped[str] = mapped_column(String(80), unique=True, index=True)
    label: Mapped[str] = mapped_column(String(120))
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    data_type: Mapped[str] = mapped_column(String(30), default="short_text")
    readonly: Mapped[bool] = mapped_column(Boolean, default=False)
    is_system: Mapped[bool] = mapped_column(Boolean, default=False)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    translation_key: Mapped[str | None] = mapped_column(String(180), nullable=True)
    searchable: Mapped[bool] = mapped_column(Boolean, default=True)
    filterable: Mapped[bool] = mapped_column(Boolean, default=True)
    chart_enabled: Mapped[bool] = mapped_column(Boolean, default=True)
    excel_import: Mapped[bool] = mapped_column(Boolean, default=True)
    excel_export: Mapped[bool] = mapped_column(Boolean, default=True)
    bulk_edit: Mapped[bool] = mapped_column(Boolean, default=True)
    is_default: Mapped[bool] = mapped_column(Boolean, default=False)
    list_width: Mapped[int | None] = mapped_column(Integer, nullable=True)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    category_links: Mapped[list["CategoryField"]] = relationship(back_populates="definition", cascade="all, delete-orphan")
    values: Mapped[list["AssetFieldValue"]] = relationship(back_populates="definition", cascade="all, delete-orphan")


class CategoryField(Base):
    __tablename__ = "category_fields"
    __table_args__ = (UniqueConstraint("category_id", "field_name"),)

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE"))
    field_name: Mapped[str] = mapped_column(String(80))
    label: Mapped[str] = mapped_column(String(100))
    field_definition_id: Mapped[int | None] = mapped_column(ForeignKey("field_definitions.id", ondelete="CASCADE"), nullable=True, index=True)
    active: Mapped[bool] = mapped_column(Boolean, default=True)
    required: Mapped[bool] = mapped_column(Boolean, default=False)
    show_in_list: Mapped[bool] = mapped_column(Boolean, default=False)
    readonly: Mapped[bool] = mapped_column(Boolean, default=False)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)

    category: Mapped[Category] = relationship(back_populates="visible_fields")
    definition: Mapped[FieldDefinition | None] = relationship(back_populates="category_links")

    @property
    def effective_label(self) -> str:
        return self.definition.label if self.definition else self.label

    @property
    def effective_readonly(self) -> bool:
        return bool(self.definition.readonly) if self.definition else bool(getattr(self, "readonly", False))


class Asset(Base):
    __tablename__ = "assets"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    category_id: Mapped[int] = mapped_column(ForeignKey("categories.id"), index=True)
    asset_tag: Mapped[str | None] = mapped_column(String(100), unique=True, nullable=True, index=True)
    name: Mapped[str] = mapped_column(String(150), index=True)
    manufacturer: Mapped[str | None] = mapped_column(String(150), nullable=True)
    model: Mapped[str | None] = mapped_column(String(150), nullable=True)
    serial_number: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True)
    status: Mapped[str | None] = mapped_column(String(60), nullable=True)
    assigned_to: Mapped[str | None] = mapped_column(String(150), nullable=True)
    assigned_on: Mapped[str | None] = mapped_column(String(20), nullable=True)
    location: Mapped[str | None] = mapped_column(String(150), nullable=True)
    room_number: Mapped[str | None] = mapped_column(String(80), nullable=True, index=True)
    parent_asset_id: Mapped[int | None] = mapped_column(ForeignKey("assets.id", ondelete="SET NULL"), nullable=True, index=True)
    department: Mapped[str | None] = mapped_column(String(150), nullable=True)
    purchase_date: Mapped[str | None] = mapped_column(String(20), nullable=True)
    warranty_until: Mapped[str | None] = mapped_column(String(20), nullable=True)
    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    image_path: Mapped[str | None] = mapped_column(String(255), nullable=True)
    visual_source: Mapped[str] = mapped_column(String(24), default="auto")

    hostname: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True)
    operating_system: Mapped[str | None] = mapped_column(String(150), nullable=True)
    os_patch_level: Mapped[str | None] = mapped_column(String(150), nullable=True)
    mac_address: Mapped[str | None] = mapped_column(String(50), nullable=True)
    ip_address: Mapped[str | None] = mapped_column(String(50), nullable=True)
    cpu: Mapped[str | None] = mapped_column(String(200), nullable=True)
    memory_gb: Mapped[str | None] = mapped_column(String(40), nullable=True)
    storage_gb: Mapped[str | None] = mapped_column(String(40), nullable=True)
    storage_details: Mapped[str | None] = mapped_column(Text, nullable=True)
    gpu_name: Mapped[str | None] = mapped_column(Text, nullable=True)
    tpm_version: Mapped[str | None] = mapped_column(String(50), nullable=True)
    memory_details: Mapped[str | None] = mapped_column(Text, nullable=True)
    antivirus: Mapped[str | None] = mapped_column(Text, nullable=True)
    mesh_group: Mapped[str | None] = mapped_column(String(180), nullable=True)
    mesh_mtype: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
    mesh_icon: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)

    mesh_node_id: Mapped[str | None] = mapped_column(String(180), unique=True, nullable=True, index=True)
    mesh_last_sync: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    mesh_last_seen: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    mesh_online: Mapped[bool | None] = mapped_column(Boolean, nullable=True, index=True)
    mesh_online_state: Mapped[str] = mapped_column(String(20), default="unknown", index=True)
    mesh_presence_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    mesh_last_online_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    mesh_presence_source: Mapped[str | None] = mapped_column(String(40), nullable=True)
    mesh_sync_status: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True)
    mesh_sync_message: Mapped[str | None] = mapped_column(Text, nullable=True)
    mesh_conflicts: Mapped[dict] = mapped_column(JSON, default=dict)
    mesh_source_data: Mapped[dict] = mapped_column(JSON, default=dict)
    mesh_hardware_data: Mapped[dict] = mapped_column(JSON, default=dict)
    mesh_software_data: Mapped[list | dict] = mapped_column(JSON, default=list)
    mesh_inventory_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    extra_data: Mapped[dict] = mapped_column(JSON, default=dict)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    category: Mapped[Category] = relationship(back_populates="assets")
    parent_asset: Mapped["Asset | None"] = relationship("Asset", remote_side="Asset.id", back_populates="child_assets", foreign_keys=[parent_asset_id])
    child_assets: Mapped[list["Asset"]] = relationship("Asset", back_populates="parent_asset", foreign_keys=[parent_asset_id])


class AssetFieldValue(Base):
    __tablename__ = "asset_field_values"
    __table_args__ = (UniqueConstraint("asset_id", "field_definition_id"),)

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    field_definition_id: Mapped[int] = mapped_column(ForeignKey("field_definitions.id", ondelete="CASCADE"), index=True)
    value_text: Mapped[str | None] = mapped_column(Text, nullable=True)
    value_number: Mapped[str | None] = mapped_column(String(100), nullable=True)
    value_boolean: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
    value_date: Mapped[str | None] = mapped_column(String(20), nullable=True)
    value_datetime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    value_json: Mapped[dict | list | None] = mapped_column(JSON, nullable=True)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    asset: Mapped[Asset] = relationship()
    definition: Mapped[FieldDefinition] = relationship(back_populates="values")


class MeshFieldMapping(Base):
    __tablename__ = "mesh_field_mappings"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    source_path: Mapped[str] = mapped_column(String(255), index=True)
    target_field_name: Mapped[str] = mapped_column(String(80), index=True)
    transform: Mapped[str] = mapped_column(String(40), default="raw")
    multi_value_mode: Mapped[str] = mapped_column(String(30), default="first")
    separator: Mapped[str] = mapped_column(String(20), default="\n")
    enabled: Mapped[bool] = mapped_column(Boolean, default=True)
    priority: Mapped[int] = mapped_column(Integer, default=0)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    update_rule: Mapped[str] = mapped_column(String(30), default="fill_empty")
    is_system: Mapped[bool] = mapped_column(Boolean, default=False)


class AssetHistory(Base):
    __tablename__ = "asset_history"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    changed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
    source: Mapped[str] = mapped_column(String(50), default="manual")
    changed_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    changes: Mapped[dict] = mapped_column(JSON, default=dict)


class StatusOption(Base):
    __tablename__ = "status_options"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(60), unique=True, index=True)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)
    active: Mapped[bool] = mapped_column(Boolean, default=True)
    use_for_issue: Mapped[bool] = mapped_column(Boolean, default=False)
    use_for_return: Mapped[bool] = mapped_column(Boolean, default=False)


class SyncRun(Base):
    __tablename__ = "sync_runs"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    source: Mapped[str] = mapped_column(String(50), default="meshcentral")
    started_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    status: Mapped[str] = mapped_column(String(30), default="running")
    devices_found: Mapped[int] = mapped_column(Integer, default=0)
    created_count: Mapped[int] = mapped_column(Integer, default=0)
    updated_count: Mapped[int] = mapped_column(Integer, default=0)
    conflict_count: Mapped[int] = mapped_column(Integer, default=0)
    error_count: Mapped[int] = mapped_column(Integer, default=0)
    message: Mapped[str | None] = mapped_column(Text, nullable=True)


class ChartDefinition(Base):
    __tablename__ = "chart_definitions"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(150), unique=True, index=True)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    chart_type: Mapped[str] = mapped_column(String(30), default="bar")
    group_field: Mapped[str] = mapped_column(String(80), default="category")
    stack_field: Mapped[str | None] = mapped_column(String(80), nullable=True)
    dashboard_visible: Mapped[bool] = mapped_column(Boolean, default=False)
    show_empty: Mapped[bool] = mapped_column(Boolean, default=False)
    max_items: Mapped[int] = mapped_column(Integer, default=12)
    sort_mode: Mapped[str] = mapped_column(String(30), default="count_desc")
    value_mode: Mapped[str] = mapped_column(String(30), default="raw")
    value_mappings: Mapped[list] = mapped_column(JSON, default=list)
    excluded_statuses: Mapped[list] = mapped_column(JSON, default=list)
    is_system: Mapped[bool] = mapped_column(Boolean, default=False)
    created_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class Language(Base):
    __tablename__ = "languages"

    code: Mapped[str] = mapped_column(String(10), primary_key=True)
    name: Mapped[str] = mapped_column(String(80))
    native_name: Mapped[str] = mapped_column(String(80))
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    is_default: Mapped[bool] = mapped_column(Boolean, default=False)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)


class Translation(Base):
    __tablename__ = "translations"
    __table_args__ = (UniqueConstraint("translation_key", "language_code"),)

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    translation_key: Mapped[str] = mapped_column(String(180), index=True)
    language_code: Mapped[str] = mapped_column(ForeignKey("languages.code", ondelete="CASCADE"), index=True)
    text_value: Mapped[str] = mapped_column(Text)
    description: Mapped[str | None] = mapped_column(String(255), nullable=True)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    username: Mapped[str] = mapped_column(String(120), unique=True, index=True)
    display_name: Mapped[str | None] = mapped_column(String(180), nullable=True)
    email: Mapped[str | None] = mapped_column(String(180), nullable=True)
    password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
    auth_source: Mapped[str] = mapped_column(String(30), default="local")
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
    is_protected: Mapped[bool] = mapped_column(Boolean, default=False)
    allowed_mesh_groups: Mapped[list] = mapped_column(JSON, default=list)
    asset_access_scopes: Mapped[list] = mapped_column(JSON, default=list)
    profile_department: Mapped[str | None] = mapped_column(String(150), nullable=True)
    profile_location: Mapped[str | None] = mapped_column(String(150), nullable=True)
    language_code: Mapped[str] = mapped_column(String(10), default="en")
    persist_browser_state: Mapped[bool] = mapped_column(Boolean, default=False)
    toast_position: Mapped[str] = mapped_column(String(30), default="top-right")
    toast_duration_seconds: Mapped[int] = mapped_column(Integer, default=6)
    theme_mode: Mapped[str] = mapped_column(String(20), default="light")
    last_login: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class JobDefinition(Base):
    __tablename__ = "job_definitions"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(180), unique=True, index=True)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    job_kind: Mapped[str] = mapped_column(String(30), default="script", index=True)
    configuration: Mapped[dict] = mapped_column(JSON, default=dict)
    system_key: Mapped[str | None] = mapped_column(String(120), unique=True, nullable=True, index=True)
    platform: Mapped[str] = mapped_column(String(30), default="windows", index=True)
    interpreter: Mapped[str] = mapped_column(String(30), default="powershell")
    source_type: Mapped[str] = mapped_column(String(30), default="inline")
    source_path: Mapped[str | None] = mapped_column(Text, nullable=True)
    inline_script: Mapped[str | None] = mapped_column(Text, nullable=True)
    arguments: Mapped[str | None] = mapped_column(Text, nullable=True)
    timeout_seconds: Mapped[int] = mapped_column(Integer, default=300)
    success_codes: Mapped[str] = mapped_column(String(120), default="0")
    enabled: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
    is_system: Mapped[bool] = mapped_column(Boolean, default=False)
    revision: Mapped[int] = mapped_column(Integer, default=1)
    created_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    updated_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    revisions: Mapped[list["JobDefinitionRevision"]] = relationship(back_populates="definition", cascade="all, delete-orphan", order_by="JobDefinitionRevision.revision.desc()")
    parameters: Mapped[list["JobDefinitionParameter"]] = relationship(back_populates="definition", cascade="all, delete-orphan", order_by="JobDefinitionParameter.sort_order")


class JobDefinitionParameter(Base):
    __tablename__ = "job_definition_parameters"
    __table_args__ = (UniqueConstraint("definition_id", "parameter_key", name="uq_job_definition_parameter_key"),)
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    definition_id: Mapped[int] = mapped_column(ForeignKey("job_definitions.id", ondelete="CASCADE"), index=True)
    parameter_key: Mapped[str] = mapped_column(String(80))
    label: Mapped[str] = mapped_column(String(140))
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    data_type: Mapped[str] = mapped_column(String(30), default="text")
    default_value: Mapped[str | None] = mapped_column(Text, nullable=True)
    required: Mapped[bool] = mapped_column(Boolean, default=False)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)
    definition: Mapped["JobDefinition"] = relationship(back_populates="parameters")


class JobDefinitionAssetParameter(Base):
    __tablename__ = "job_definition_asset_parameters"
    __table_args__ = (UniqueConstraint("definition_parameter_id", "asset_id", name="uq_job_definition_asset_parameter"),)
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    definition_parameter_id: Mapped[int] = mapped_column(ForeignKey("job_definition_parameters.id", ondelete="CASCADE"), index=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    value_text: Mapped[str | None] = mapped_column(Text, nullable=True)
    updated_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    parameter: Mapped["JobDefinitionParameter"] = relationship()
    asset: Mapped["Asset"] = relationship()


class JobDefinitionRevision(Base):
    __tablename__ = "job_definition_revisions"
    __table_args__ = (UniqueConstraint("definition_id", "revision", name="uq_job_definition_revision"),)
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    definition_id: Mapped[int] = mapped_column(ForeignKey("job_definitions.id", ondelete="CASCADE"), index=True)
    revision: Mapped[int] = mapped_column(Integer)
    snapshot: Mapped[dict] = mapped_column(JSON, default=dict)
    changed_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    definition: Mapped["JobDefinition"] = relationship(back_populates="revisions")


class SoftwarePackage(Base):
    __tablename__ = "software_packages"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(180), unique=True, index=True)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    package_type: Mapped[str] = mapped_column(String(40), default="command")
    enabled: Mapped[bool] = mapped_column(Boolean, default=True)
    is_system: Mapped[bool] = mapped_column(Boolean, default=False)
    command_windows: Mapped[str | None] = mapped_column(Text, nullable=True)
    command_linux: Mapped[str | None] = mapped_column(Text, nullable=True)
    command_macos: Mapped[str | None] = mapped_column(Text, nullable=True)
    command_android: Mapped[str | None] = mapped_column(Text, nullable=True)
    callback_timeout_minutes: Mapped[int] = mapped_column(Integer, default=30)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    parameters: Mapped[list["SoftwarePackageParameter"]] = relationship(back_populates="package", cascade="all, delete-orphan", order_by="SoftwarePackageParameter.sort_order")
    jobs: Mapped[list["SoftwareJob"]] = relationship(back_populates="package")


class SoftwarePackageParameter(Base):
    __tablename__ = "software_package_parameters"
    __table_args__ = (UniqueConstraint("package_id", "name"),)
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    package_id: Mapped[int] = mapped_column(ForeignKey("software_packages.id", ondelete="CASCADE"), index=True)
    name: Mapped[str] = mapped_column(String(80))
    label: Mapped[str] = mapped_column(String(120))
    data_type: Mapped[str] = mapped_column(String(30), default="text")
    required: Mapped[bool] = mapped_column(Boolean, default=False)
    secret: Mapped[bool] = mapped_column(Boolean, default=False)
    default_value: Mapped[str | None] = mapped_column(Text, nullable=True)
    validation_regex: Mapped[str | None] = mapped_column(String(255), nullable=True)
    sort_order: Mapped[int] = mapped_column(Integer, default=0)
    package: Mapped[SoftwarePackage] = relationship(back_populates="parameters")


class SoftwareJob(Base):
    __tablename__ = "software_jobs"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    package_id: Mapped[int] = mapped_column(ForeignKey("software_packages.id"), index=True)
    status: Mapped[str] = mapped_column(String(30), default="created", index=True)
    job_type: Mapped[str] = mapped_column(String(50), default="software_inventory", index=True)
    action: Mapped[str] = mapped_column(String(30), default="run", index=True)
    priority: Mapped[int] = mapped_column(Integer, default=100, index=True)
    platform: Mapped[str] = mapped_column(String(30), default="unknown")
    parameters: Mapped[dict] = mapped_column(JSON, default=dict)
    result_data: Mapped[dict] = mapped_column(JSON, default=dict)
    attempt_count: Mapped[int] = mapped_column(Integer, default=0)
    max_attempts: Mapped[int] = mapped_column(Integer, default=1)
    scheduled_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
    callback_token_hash: Mapped[str] = mapped_column(String(64), index=True)
    callback_expires_at: Mapped[datetime] = mapped_column(DateTime)
    callback_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    command_preview: Mapped[str | None] = mapped_column(Text, nullable=True)
    resolved_script: Mapped[str | None] = mapped_column(Text, nullable=True)
    meshctrl_stdout: Mapped[str | None] = mapped_column(Text, nullable=True)
    meshctrl_stderr: Mapped[str | None] = mapped_column(Text, nullable=True)
    exit_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
    message: Mapped[str | None] = mapped_column(Text, nullable=True)
    log_text: Mapped[str] = mapped_column(Text, default="")
    created_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
    sent_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    asset: Mapped[Asset] = relationship()
    package: Mapped[SoftwarePackage] = relationship(back_populates="jobs")
    events: Mapped[list["JobEvent"]] = relationship(back_populates="job", cascade="all, delete-orphan", order_by="JobEvent.created_at")


class AssetJobState(Base):
    __tablename__ = "asset_job_states"
    __table_args__ = (
        UniqueConstraint("asset_id", "state_key", name="uq_asset_job_state_asset_key"),
        Index("ix_asset_job_states_key_status", "state_key", "last_status"),
        Index("ix_asset_job_states_key_success", "state_key", "last_success_at"),
        Index("ix_asset_job_states_asset_key", "asset_id", "state_key"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    state_key: Mapped[str] = mapped_column(String(120), index=True)
    job_type: Mapped[str] = mapped_column(String(50), index=True)
    job_definition_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
    definition_revision: Mapped[int | None] = mapped_column(Integer, nullable=True)
    last_success_revision: Mapped[int | None] = mapped_column(Integer, nullable=True)
    last_job_id: Mapped[int | None] = mapped_column(ForeignKey("software_jobs.id", ondelete="SET NULL"), nullable=True, index=True)
    last_status: Mapped[str] = mapped_column(String(30), default="created", index=True)
    last_status_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
    last_created_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    last_sent_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    last_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    last_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    last_success_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
    execution_count: Mapped[int] = mapped_column(Integer, default=0)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    asset: Mapped[Asset] = relationship()
    last_job: Mapped[SoftwareJob | None] = relationship()


class JobEvent(Base):
    __tablename__ = "job_events"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    job_id: Mapped[int] = mapped_column(ForeignKey("software_jobs.id", ondelete="CASCADE"), index=True)
    attempt: Mapped[int] = mapped_column(Integer, default=0)
    event_type: Mapped[str] = mapped_column(String(40), default="status", index=True)
    status: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True)
    message: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_by: Mapped[str | None] = mapped_column(String(150), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
    job: Mapped["SoftwareJob"] = relationship(back_populates="events")


class InstalledSoftware(Base):
    __tablename__ = "installed_software"
    __table_args__ = (UniqueConstraint("asset_id", "name", "version", "publisher", "architecture", name="uq_installed_software_identity"),)
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True)
    name: Mapped[str] = mapped_column(String(300), index=True)
    version: Mapped[str] = mapped_column(String(160), default="")
    publisher: Mapped[str] = mapped_column(String(220), default="")
    architecture: Mapped[str] = mapped_column(String(40), default="")
    platform: Mapped[str] = mapped_column(String(40), default="unknown", index=True)
    install_date: Mapped[str | None] = mapped_column(String(40), nullable=True)
    source: Mapped[str] = mapped_column(String(50), default="meshcentral-inventory")
    first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
    asset: Mapped[Asset] = relationship()
