"""Nexus — Domain Models
SQLAlchemy ORM models for all database tables.
"""

import datetime
from datetime import timezone
import uuid
from sqlalchemy import (
    Column, Integer, String, Boolean,
    DateTime, Text, ForeignKey, Index, JSON
)
from sqlalchemy.orm import declarative_base, relationship


def _utcnow():
    """Callable default for DateTime columns — evaluated per-row, not at module import"""
    return datetime.datetime.now(timezone.utc)


Base = declarative_base()


# ──────────────────────────────────────────────
#  Platform & Node (资产组织层)
# ──────────────────────────────────────────────

class Platform(Base):
    """Asset type template — defines what kind of asset this is"""
    __tablename__ = "platforms"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), unique=True, nullable=False, comment="平台名称: Linux服务器/Windows/MySQL")
    category = Column(String(50), nullable=False, comment="分类: host/database/device")
    type = Column(String(50), nullable=False, comment="类型: linux/windows/mysql/switch")
    default_protocols = Column(JSON, nullable=True, comment="默认协议: [{name:'ssh',port:22,primary:true}]")
    charset = Column(String(20), default="utf-8", comment="默认字符集")
    created_at = Column(DateTime, default=_utcnow)

    assets = relationship("Server", back_populates="platform")


class Node(Base):
    """Tree-structured asset grouping"""
    __tablename__ = "nodes"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="节点名称")
    parent_id = Column(Integer, ForeignKey("nodes.id", ondelete="SET NULL"), nullable=True, comment="父节点")
    sort_order = Column(Integer, default=0, comment="排序权重")
    created_at = Column(DateTime, default=_utcnow)

    parent = relationship("Node", remote_side=[id], back_populates="children")
    children = relationship("Node", back_populates="parent")
    assets = relationship("Server", back_populates="node")

    __table_args__ = (
        Index('idx_nodes_parent_id', 'parent_id'),
    )


# ──────────────────────────────────────────────
#  Server (核心资产表 — 保持原名，扩展字段)
# ──────────────────────────────────────────────

class Server(Base):
    """Sub-server configuration / Asset"""
    __tablename__ = "servers"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), unique=True, nullable=False, comment="服务器名称")
    domain = Column(String(255), nullable=False, comment="域名或IP")
    port = Column(Integer, default=22, comment="SSH端口")
    username = Column(String(100), default="root", comment="SSH用户名")
    auth_method = Column(String(20), default="key", comment="认证方式: key/password")
    password = Column(String(255), nullable=True, comment="SSH密码(加密存储)")
    ssh_key_path = Column(String(500), nullable=True, comment="SSH私钥路径")
    # Text columns: RSA 4096 private key PEM is ~3.2 KB; Fernet-encrypted ~4.3 KB.
    # String(500) would cause DataError in MySQL strict mode for large keys.
    ssh_key_private = Column(Text, nullable=True, comment="加密后的私钥内容(Fernet)")
    ssh_key_public = Column(Text, nullable=True, comment="公钥内容")
    ssh_key_configured = Column(Boolean, default=False, comment="是否已配置SSH Key")
    agent_port = Column(Integer, default=8601, comment="Agent API端口")
    agent_api_key = Column(String(255), nullable=True, comment="Agent API密钥")
    preset_id = Column(Integer, nullable=True, comment="关联的密码预设ID(SSH认证)")
    ssh_key_preset_id = Column(Integer, nullable=True, comment="关联的密钥预设ID(SSH认证)")
    description = Column(Text, nullable=True, comment="备注说明")
    target_path = Column(String(500), nullable=True, comment="推送目标路径")
    category = Column(String(100), nullable=True, comment="服务器分类(旧字段，保留兼容)")

    # ── 新增：资产组织字段 ──
    platform_id = Column(Integer, ForeignKey("platforms.id", ondelete="SET NULL"), nullable=True, comment="平台类型ID")
    node_id = Column(Integer, ForeignKey("nodes.id", ondelete="SET NULL"), nullable=True, comment="所属节点ID")
    protocols = Column(JSON, nullable=True, comment="实例级协议覆盖: [{name:'ssh',port:2222}]")
    extra_attrs = Column(JSON, nullable=True, comment="扩展属性: {os:'ubuntu',arch:'x86_64',cpu:8,mem:'32G'}")

    # Status (from Agent heartbeat)
    is_online = Column(Boolean, default=False, comment="是否在线")
    last_heartbeat = Column(DateTime, nullable=True, comment="最后心跳时间")
    system_info = Column(Text, nullable=True, comment="系统信息JSON(Agent上报)")
    agent_version = Column(String(20), nullable=True, comment="Agent版本")

    # ── 新增：连接状态 ──
    connectivity = Column(String(20), default="unknown", comment="连接状态: ok/err/unknown")
    last_checked_at = Column(DateTime, nullable=True, comment="上次连接测试时间")

    # Metadata
    created_at = Column(DateTime, default=_utcnow)
    updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow)

    # Relationships
    sync_logs = relationship("SyncLog", back_populates="server", cascade="all, delete-orphan")
    platform = relationship("Platform", back_populates="assets")
    node = relationship("Node", back_populates="assets")

    __table_args__ = (
        Index('idx_servers_is_online', 'is_online'),
        Index('idx_servers_category', 'category'),
        Index('idx_servers_platform_id', 'platform_id'),
        Index('idx_servers_node_id', 'node_id'),
    )


class SyncLog(Base):
    """Push operation log"""
    __tablename__ = "sync_logs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), nullable=False)
    source_path = Column(String(500), nullable=False, comment="源目录")
    target_path = Column(String(500), nullable=False, comment="目标目录")
    trigger_type = Column(String(20), comment="触发类型: manual/schedule/batch")
    operator = Column(String(100), nullable=True, comment="操作人用户名")
    status = Column(String(20), comment="pending/running/success/failed")
    sync_mode = Column(String(20), default="incremental", comment="同步模式: incremental/full/overwrite/checksum")
    files_total = Column(Integer, default=0)
    files_transferred = Column(Integer, default=0)
    files_skipped = Column(Integer, default=0)
    bytes_transferred = Column(Integer, default=0)
    duration_seconds = Column(Integer, default=0)
    diff_summary = Column(Text, nullable=True, comment="rsync输出摘要")
    error_message = Column(Text, nullable=True)
    started_at = Column(DateTime, default=_utcnow)
    finished_at = Column(DateTime, nullable=True)

    server = relationship("Server", back_populates="sync_logs")

    __table_args__ = (
        Index('idx_sync_logs_srv_start', 'server_id', 'started_at'),
    )


class Admin(Base):
    """Admin user for web login"""
    __tablename__ = "admins"

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(100), unique=True, nullable=False)
    password_hash = Column(String(255), nullable=False)
    email = Column(String(255), nullable=True)
    totp_secret = Column(String(64), nullable=True)
    totp_enabled = Column(Boolean, default=False)
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime, default=_utcnow)
    updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow)
    last_login = Column(DateTime, nullable=True)

    token_version = Column(Integer, default=0, comment="令牌版本(重用时递增使旧token失效)")


class LoginAttempt(Base):
    """Login attempt log for brute-force protection"""
    __tablename__ = "login_attempts"

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(100), nullable=False)
    ip_address = Column(String(45), nullable=False)
    attempted_at = Column(DateTime, default=_utcnow)
    success = Column(Boolean, default=False)

    __table_args__ = (
        Index('idx_login_attempts_user_time', 'username', 'attempted_at'),
    )


class Setting(Base):
    """Key-value system settings (including brand config)"""
    __tablename__ = "settings"

    key = Column(String(100), primary_key=True)
    value = Column(Text, nullable=True)
    updated_at = Column(DateTime, default=_utcnow)


class PasswordPreset(Base):
    """SSH password preset — encrypted storage"""
    __tablename__ = "password_presets"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="预设名称")
    encrypted_pw = Column(String(500), nullable=False, comment="加密后的密码")
    created_at = Column(DateTime, default=_utcnow)


class SshKeyPreset(Base):
    """SSH key preset — encrypted private key storage"""
    __tablename__ = "ssh_key_presets"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="预设名称")
    encrypted_private_key = Column(Text, nullable=False, comment="加密后的私钥内容(Fernet)")
    public_key = Column(Text, nullable=True, comment="公钥内容(不加密)")
    created_at = Column(DateTime, default=_utcnow)


class PushSchedule(Base):
    """Scheduled task — cron-triggered file push OR script execution."""
    __tablename__ = "push_schedules"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="调度名称")

    # ── Schedule type ──
    # 'push': rsync file sync (default, original behaviour)
    # 'script': execute a shell command / script via Agent
    schedule_type = Column(String(20), default="push", comment="调度类型: push | script")

    # ── Push-specific fields (schedule_type = 'push') ──
    source_path = Column(String(500), nullable=True, comment="推送源目录（push 类型必填）")
    sync_mode = Column(String(20), default="incremental", comment="同步模式: incremental/full/overwrite/checksum")

    # ── Script-specific fields (schedule_type = 'script') ──
    script_id = Column(Integer, ForeignKey("scripts.id", ondelete="SET NULL"), nullable=True,
                       comment="脚本库引用（script 类型，可留空使用 script_content）")
    script_content = Column(Text, nullable=True, comment="临时命令内容（script 类型，不保存到脚本库）")
    exec_timeout = Column(Integer, default=60, comment="执行超时（秒）")
    long_task = Column(Boolean, default=False, comment="长任务 nohup 模式")

    # ── Common fields ──
    server_ids = Column(Text, nullable=False, comment="JSON数组: 目标服务器ID列表")
    run_mode = Column(String(10), default="once", comment="运行模式: once(一次性) | cron(循环)")
    cron_expr = Column(String(100), nullable=True, comment="cron表达式 (run_mode=cron时必填)")
    fire_at = Column(DateTime, nullable=True, comment="一次性执行时间 (run_mode=once时必填)")
    enabled = Column(Boolean, default=True)
    last_run_at = Column(DateTime, nullable=True)
    created_at = Column(DateTime, default=_utcnow)

    __table_args__ = (
        Index('idx_push_schedules_enabled', 'enabled'),
    )


class PushRetryJob(Base):
    """Failed push retry queue"""
    __tablename__ = "push_retry_jobs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), nullable=False)
    server_name = Column(String(100), comment="冗余字段便于列表显示")
    operator = Column(String(100), nullable=True, comment="操作人用户名")
    source_path = Column(String(500), nullable=False)
    target_path = Column(String(500), nullable=False)
    status = Column(String(20), default="pending")
    retry_count = Column(Integer, default=0)
    max_retries = Column(Integer, default=100)
    next_retry_at = Column(DateTime)
    last_error = Column(Text)
    created_at = Column(DateTime, default=_utcnow)
    updated_at = Column(DateTime, default=_utcnow)

    server = relationship("Server")

    __table_args__ = (
        Index('idx_push_retry_pending', 'status', 'next_retry_at'),
    )


class AuditLog(Base):
    """Audit log — record all key operations"""
    __tablename__ = "audit_logs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    admin_username = Column(String(100), nullable=True, comment="操作人用户名")
    action = Column(String(50), nullable=False, comment="操作类型")
    target_type = Column(String(50), nullable=True, comment="目标类型")
    target_id = Column(Integer, nullable=True, comment="目标ID")
    detail = Column(Text, nullable=True, comment="操作详情JSON")
    ip_address = Column(String(45), nullable=True, comment="操作IP")
    created_at = Column(DateTime, default=_utcnow)

    __table_args__ = (
        Index('idx_audit_logs_action_time', 'action', 'created_at'),
        Index('idx_audit_logs_created_at', 'created_at'),
    )


class Script(Base):
    """Script library — reusable shell scripts"""
    __tablename__ = "scripts"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="脚本名称")
    category = Column(String(50), default="ops", comment="分类: ops/deploy/check/cleanup")
    content = Column(Text, nullable=False, comment="Shell命令内容")
    description = Column(Text, nullable=True, comment="说明")
    created_by = Column(String(100), nullable=True, comment="创建人")
    created_at = Column(DateTime, default=_utcnow)
    updated_at = Column(DateTime, default=_utcnow)


class ScriptExecution(Base):
    """Script execution log — results per server"""
    __tablename__ = "script_executions"

    id = Column(Integer, primary_key=True, autoincrement=True)
    script_id = Column(Integer, ForeignKey("scripts.id", ondelete="SET NULL"), nullable=True, comment="关联脚本(手动输入时为null)")
    command = Column(Text, nullable=False, comment="实际执行的命令")
    server_ids = Column(Text, nullable=False, comment="JSON数组: 目标服务器ID列表")
    credential_id = Column(Integer, ForeignKey("db_credentials.id", ondelete="SET NULL"), nullable=True, comment="DB凭据替换$DB_*变量")
    status = Column(String(20), default="pending", comment="pending/running/completed/failed")
    results = Column(Text, nullable=True, comment="JSON: 每台服务器执行结果{server_id: {stdout,stderr,exit_code}}")
    operator = Column(String(100), nullable=True, comment="操作人用户名")
    started_at = Column(DateTime, default=_utcnow)
    completed_at = Column(DateTime, nullable=True)

    script = relationship("Script")

    __table_args__ = (
        Index('idx_script_exec_script_id', 'script_id'),
    )


class DbCredential(Base):
    """Database credentials — encrypted storage for $DB_* variable substitution"""
    __tablename__ = "db_credentials"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="凭据名称")
    db_type = Column(String(20), default="mysql", comment="数据库类型: mysql/postgresql/etc")
    host = Column(String(255), default="localhost", comment="数据库主机")
    port = Column(Integer, default=3306, comment="数据库端口")
    username = Column(String(100), nullable=False, comment="数据库用户名")
    encrypted_password = Column(String(500), nullable=False, comment="加密后的密码")
    database = Column(String(100), nullable=True, comment="数据库库名")
    created_at = Column(DateTime, default=_utcnow)


# ──────────────────────────────────────────────
#  Web SSH 会话 & 命令日志
# ──────────────────────────────────────────────

class AlertLog(Base):
    """Persistent alert & recovery history."""
    __tablename__ = "alert_logs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), nullable=False)
    server_name = Column(String(100), nullable=True, comment="冗余服务器名，便于列表显示")
    alert_type = Column(String(20), nullable=False, comment="cpu|mem|disk|time_drift|system")
    value = Column(String(50), nullable=True, comment="指标值（百分比或描述）")
    is_recovery = Column(Boolean, default=False, comment="True=恢复通知")
    created_at = Column(DateTime, default=_utcnow)

    server = relationship("Server")

    __table_args__ = (
        Index("idx_alert_logs_server_id", "server_id"),
        Index("idx_alert_logs_created_at", "created_at"),
        Index("idx_alert_logs_type", "alert_type"),
    )


class SshSession(Base):
    """Web SSH session lifecycle tracking"""
    __tablename__ = "ssh_sessions"

    id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
    server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), nullable=False, comment="服务器ID")
    admin_id = Column(Integer, ForeignKey("admins.id", ondelete="SET NULL"), nullable=True, comment="操作人ID")
    remote_addr = Column(String(45), nullable=True, comment="客户端IP")
    status = Column(String(20), default="active", comment="active/closed")
    started_at = Column(DateTime, default=_utcnow)
    closed_at = Column(DateTime, nullable=True)

    server = relationship("Server")
    admin = relationship("Admin")
    command_logs = relationship("CommandLog", back_populates="session", cascade="all, delete-orphan")

    __table_args__ = (
        Index('idx_ssh_sessions_server_id', 'server_id'),
        Index('idx_ssh_sessions_admin_id', 'admin_id'),
    )


class CommandLog(Base):
    """SSH command log — record every command executed in terminal"""
    __tablename__ = "command_logs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    session_id = Column(String(36), ForeignKey("ssh_sessions.id", ondelete="CASCADE"), nullable=True, comment="SSH会话ID")
    server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), nullable=False, comment="服务器ID")
    admin_id = Column(Integer, ForeignKey("admins.id", ondelete="SET NULL"), nullable=True, comment="操作人ID")
    command = Column(String(2000), nullable=False, comment="执行的命令")
    remote_addr = Column(String(45), nullable=True, comment="客户端IP")
    created_at = Column(DateTime, default=_utcnow)

    session = relationship("SshSession", back_populates="command_logs")
    server = relationship("Server")
    admin = relationship("Admin")

    __table_args__ = (
        Index('idx_cmdlog_srv_time', 'server_id', 'created_at'),
        Index('idx_cmdlog_session_id', 'session_id'),
    )


# ──────────────────────────────────────────────
#  Quick Commands (终端快捷命令)
# ──────────────────────────────────────────────

class QuickCommand(Base):
    """Terminal quick commands — persistent across browsers, shareable across users"""
    __tablename__ = "quick_commands"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False, comment="命令名称")
    cmd = Column(String(500), nullable=False, comment="命令内容")
    is_builtin = Column(Boolean, default=False, comment="系统内置标记(不可删除/编辑)")
    sort_order = Column(Integer, default=0, comment="排序权重")
    created_by = Column(String(100), nullable=True, comment="创建人")
    created_at = Column(DateTime, default=_utcnow)
    updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow)