"""
SQLAlchemy table definitions for the SecureVector desktop application.

Tables:
- schema_version: Database schema version tracking
- threat_intel_records: Historical analysis results
- custom_rules: User-created detection rules
- rule_overrides: Modifications to community rules
- app_settings: Application preferences (singleton)
"""

from datetime import datetime
from typing import Optional

from sqlalchemy import (
    Boolean,
    Column,
    DateTime,
    Float,
    Integer,
    String,
    Text,
    CheckConstraint,
    MetaData,
    Table,
)
from sqlalchemy.dialects.sqlite import JSON

# Use naming convention for constraints
convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

metadata = MetaData(naming_convention=convention)

# Schema version table for migrations
schema_version = Table(
    "schema_version",
    metadata,
    Column("version", Integer, primary_key=True),
    Column(
        "applied_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
    ),
    Column("description", Text, nullable=False),
)

# Threat intel records table
threat_intel_records = Table(
    "threat_intel_records",
    metadata,
    Column("id", String(36), primary_key=True),  # UUID
    Column("request_id", String(64), nullable=True, index=True),
    Column("text_content", Text, nullable=True),  # Optional for privacy
    Column("text_hash", String(64), nullable=False, index=True),
    Column("text_length", Integer, nullable=False),
    Column("is_threat", Boolean, nullable=False, index=True),
    Column("threat_type", String(50), nullable=True, index=True),
    Column("risk_score", Integer, nullable=False),
    Column("confidence", Float, nullable=False),
    Column("matched_rules", JSON, nullable=False),  # Array of matched rules
    Column("source_identifier", String(255), nullable=True, index=True),
    Column("session_id", String(64), nullable=True, index=True),
    Column("processing_time_ms", Integer, nullable=False),
    Column(
        "created_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        index=True,
    ),
    Column("metadata", JSON, nullable=True),
    # Constraints
    CheckConstraint("risk_score >= 0 AND risk_score <= 100", name="risk_score_range"),
    CheckConstraint("confidence >= 0 AND confidence <= 1", name="confidence_range"),
)

# Custom rules table
custom_rules = Table(
    "custom_rules",
    metadata,
    Column("id", String(100), primary_key=True),
    Column("name", String(255), nullable=False),
    Column("category", String(50), nullable=False, index=True),
    Column("description", Text, nullable=False),
    Column("severity", String(20), nullable=False),
    Column("patterns", JSON, nullable=False),  # Array of regex patterns
    Column("enabled", Boolean, nullable=False, default=True),
    Column("metadata", JSON, nullable=True),
    Column(
        "created_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
    ),
    Column(
        "updated_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        onupdate=datetime.utcnow,
    ),
    # Constraints
    CheckConstraint(
        "severity IN ('low', 'medium', 'high', 'critical')",
        name="severity_values",
    ),
)

# Rule overrides table for community rules
rule_overrides = Table(
    "rule_overrides",
    metadata,
    Column("id", String(36), primary_key=True),  # UUID
    Column("original_rule_id", String(100), nullable=False, unique=True),
    Column("enabled", Boolean, nullable=True),
    Column("severity", String(20), nullable=True),
    Column("patterns", JSON, nullable=True),  # Override patterns
    Column(
        "created_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
    ),
    Column(
        "updated_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        onupdate=datetime.utcnow,
    ),
    # Constraints
    CheckConstraint(
        "severity IS NULL OR severity IN ('low', 'medium', 'high', 'critical')",
        name="override_severity_values",
    ),
)

# App settings table (singleton - always id=1)
app_settings = Table(
    "app_settings",
    metadata,
    Column("id", Integer, primary_key=True, default=1),
    Column("theme", String(20), nullable=False, default="system"),
    Column("server_port", Integer, nullable=False, default=8741),
    Column("server_host", String(255), nullable=False, default="127.0.0.1"),
    Column("retention_days", Integer, nullable=False, default=30),
    Column("store_text_content", Boolean, nullable=False, default=True),
    Column("notifications_enabled", Boolean, nullable=False, default=True),
    Column("launch_on_startup", Boolean, nullable=False, default=False),
    Column("minimize_to_tray", Boolean, nullable=False, default=True),
    Column("window_width", Integer, nullable=True),
    Column("window_height", Integer, nullable=True),
    Column("window_x", Integer, nullable=True),
    Column("window_y", Integer, nullable=True),
    # Cloud mode fields (added in schema v3)
    Column("cloud_mode_enabled", Boolean, nullable=False, default=False),
    Column("cloud_user_email", String(255), nullable=True),
    Column("cloud_connected_at", DateTime, nullable=True),
    Column(
        "updated_at",
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        onupdate=datetime.utcnow,
    ),
    # Constraints
    CheckConstraint("id = 1", name="singleton"),
    CheckConstraint(
        "theme IN ('system', 'light', 'dark')",
        name="theme_values",
    ),
    CheckConstraint(
        "server_port >= 1024 AND server_port <= 65535",
        name="port_range",
    ),
    CheckConstraint(
        "retention_days >= 1 AND retention_days <= 365",
        name="retention_range",
    ),
)


# SQL for creating tables (SQLite-specific)
SCHEMA_SQL = """
-- Schema Version 1

CREATE TABLE IF NOT EXISTS schema_version (
    version INTEGER PRIMARY KEY,
    applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    description TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS threat_intel_records (
    id TEXT PRIMARY KEY,
    request_id TEXT,
    text_content TEXT,
    text_hash TEXT NOT NULL,
    text_length INTEGER NOT NULL,
    is_threat INTEGER NOT NULL,
    threat_type TEXT,
    risk_score INTEGER NOT NULL CHECK (risk_score >= 0 AND risk_score <= 100),
    confidence REAL NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
    matched_rules TEXT NOT NULL,
    source_identifier TEXT,
    session_id TEXT,
    processing_time_ms INTEGER NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    metadata TEXT,
    -- LLM Review fields
    llm_reviewed INTEGER DEFAULT 0,
    llm_agrees INTEGER DEFAULT 1,
    llm_confidence REAL DEFAULT 0,
    llm_explanation TEXT DEFAULT NULL,
    llm_recommendation TEXT DEFAULT NULL,
    llm_risk_adjustment INTEGER DEFAULT 0,
    llm_model_used TEXT DEFAULT NULL,
    llm_tokens_used INTEGER DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_threat_intel_created_at ON threat_intel_records(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_threat_intel_is_threat ON threat_intel_records(is_threat);
CREATE INDEX IF NOT EXISTS idx_threat_intel_threat_type ON threat_intel_records(threat_type);
CREATE INDEX IF NOT EXISTS idx_threat_intel_source ON threat_intel_records(source_identifier);
CREATE INDEX IF NOT EXISTS idx_threat_intel_hash ON threat_intel_records(text_hash);
CREATE INDEX IF NOT EXISTS idx_threat_intel_request_id ON threat_intel_records(request_id);

CREATE TABLE IF NOT EXISTS custom_rules (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    category TEXT NOT NULL,
    description TEXT NOT NULL,
    severity TEXT NOT NULL CHECK (severity IN ('low', 'medium', 'high', 'critical')),
    patterns TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    metadata TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_custom_rules_category ON custom_rules(category);

CREATE TABLE IF NOT EXISTS rule_overrides (
    id TEXT PRIMARY KEY,
    original_rule_id TEXT NOT NULL UNIQUE,
    enabled INTEGER,
    severity TEXT CHECK (severity IS NULL OR severity IN ('low', 'medium', 'high', 'critical')),
    patterns TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS app_settings (
    id INTEGER PRIMARY KEY CHECK (id = 1),
    theme TEXT NOT NULL DEFAULT 'system' CHECK (theme IN ('system', 'light', 'dark')),
    server_port INTEGER NOT NULL DEFAULT 8741 CHECK (server_port >= 1024 AND server_port <= 65535),
    server_host TEXT NOT NULL DEFAULT '127.0.0.1',
    retention_days INTEGER NOT NULL DEFAULT 30 CHECK (retention_days >= 1 AND retention_days <= 365),
    store_text_content INTEGER NOT NULL DEFAULT 1,
    notifications_enabled INTEGER NOT NULL DEFAULT 1,
    launch_on_startup INTEGER NOT NULL DEFAULT 0,
    minimize_to_tray INTEGER NOT NULL DEFAULT 1,
    window_width INTEGER,
    window_height INTEGER,
    window_x INTEGER,
    window_y INTEGER,
    cloud_mode_enabled INTEGER NOT NULL DEFAULT 0,
    cloud_user_email TEXT,
    cloud_connected_at TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- Initialize singleton settings row if not exists
INSERT OR IGNORE INTO app_settings (id) VALUES (1);
"""

# Current schema version
CURRENT_SCHEMA_VERSION = 28
SCHEMA_DESCRIPTION = (
    "v20: hash-chain tool_call_audit for tamper-evidence; "
    "v21: device_id on scans + audit rows; "
    "v22: external_forwarders — per-destination SIEM config (Splunk/Datadog/webhook/OTLP); "
    "v23: external_forward_outbox — fan-out queue, at-least-once per destination; "
    "v24: siem_forwarding_enabled — global kill-switch in app_settings; "
    "v25: SIEM forwarder redaction_level allows 'full' tier (raw_data + llm_output); "
    "v26: SIEM forwarder min_severity + rate_limit_per_minute (SOC signal/noise tuning); "
    "v27: drop kind CHECK on external_forwarders — allow new kinds (e.g. 'file') via app-layer validation; "
    "v28: lifetime events_sent counter on external_forwarders (per-destination health)"
)

# Migration SQL for v19
MIGRATION_V19_SQL = """
-- Schema Version 19: Skill Scanner Policy Engine

CREATE TABLE IF NOT EXISTS skill_permissions (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    category        TEXT NOT NULL CHECK (category IN ('network', 'env_var', 'file_path', 'shell_command')),
    pattern         TEXT NOT NULL,
    classification  TEXT NOT NULL CHECK (classification IN ('safe', 'review', 'dangerous')),
    label           TEXT NOT NULL DEFAULT '',
    is_default      INTEGER NOT NULL DEFAULT 1,
    enabled         INTEGER NOT NULL DEFAULT 1,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(category, pattern)
);

CREATE INDEX IF NOT EXISTS idx_skill_permissions_category
    ON skill_permissions (category, classification);

CREATE INDEX IF NOT EXISTS idx_skill_permissions_enabled
    ON skill_permissions (enabled);

CREATE TABLE IF NOT EXISTS skill_trusted_publishers (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    publisher_name  TEXT NOT NULL UNIQUE,
    trust_level     TEXT NOT NULL DEFAULT 'trusted' CHECK (trust_level IN ('trusted', 'untrusted')),
    is_default      INTEGER NOT NULL DEFAULT 1,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS skill_policy_config (
    id                      INTEGER PRIMARY KEY CHECK (id = 1),
    policy_enabled          INTEGER NOT NULL DEFAULT 1,
    risk_weight_network     INTEGER NOT NULL DEFAULT 2,
    risk_weight_env_var     INTEGER NOT NULL DEFAULT 1,
    risk_weight_shell_exec  INTEGER NOT NULL DEFAULT 5,
    risk_weight_code_exec   INTEGER NOT NULL DEFAULT 5,
    risk_weight_dynamic_import INTEGER NOT NULL DEFAULT 4,
    risk_weight_file_write  INTEGER NOT NULL DEFAULT 3,
    risk_weight_base64      INTEGER NOT NULL DEFAULT 0,
    risk_weight_compiled    INTEGER NOT NULL DEFAULT 3,
    risk_weight_rule_match  INTEGER NOT NULL DEFAULT 3,
    risk_weight_missing_manifest INTEGER NOT NULL DEFAULT 0,
    risk_weight_symlink     INTEGER NOT NULL DEFAULT 3,
    threshold_allow         INTEGER NOT NULL DEFAULT 8,
    threshold_warn          INTEGER NOT NULL DEFAULT 15,
    updated_at              TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

INSERT OR IGNORE INTO skill_policy_config (id) VALUES (1);
"""

# Migration SQL for v18
MIGRATION_V18_SQL = """
-- Schema Version 18: Add skill scan records for OpenClaw Skill Scanner

CREATE TABLE IF NOT EXISTS skill_scan_records (
    id                TEXT PRIMARY KEY,
    scanned_path      TEXT NOT NULL,
    skill_name        TEXT NOT NULL,
    scan_timestamp    TEXT NOT NULL,
    invocation_source TEXT NOT NULL DEFAULT 'cli' CHECK (invocation_source IN ('cli', 'ui')),
    risk_level        TEXT NOT NULL CHECK (risk_level IN ('HIGH', 'MEDIUM', 'LOW')),
    findings_count    INTEGER NOT NULL DEFAULT 0,
    findings_json     TEXT NOT NULL DEFAULT '[]',
    manifest_present  INTEGER NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_skill_scan_records_timestamp
    ON skill_scan_records (scan_timestamp DESC);

CREATE INDEX IF NOT EXISTS idx_skill_scan_records_risk_level
    ON skill_scan_records (risk_level);
"""

# Migration SQL for v12
MIGRATION_V12_SQL = """
-- Schema Version 12: Add LLM cost tracking

CREATE TABLE IF NOT EXISTS llm_cost_records (
    id              TEXT PRIMARY KEY,
    agent_id        TEXT NOT NULL,
    provider        TEXT NOT NULL,
    model_id        TEXT NOT NULL,
    request_id      TEXT,
    input_tokens    INTEGER NOT NULL DEFAULT 0,
    output_tokens   INTEGER NOT NULL DEFAULT 0,
    input_cost_usd  REAL NOT NULL DEFAULT 0.0,
    output_cost_usd REAL NOT NULL DEFAULT 0.0,
    total_cost_usd  REAL NOT NULL DEFAULT 0.0,
    rate_input      REAL,
    rate_output     REAL,
    pricing_known   INTEGER NOT NULL DEFAULT 1,
    recorded_at     TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_cost_records_agent ON llm_cost_records(agent_id, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_cost_records_provider ON llm_cost_records(provider, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_cost_records_model ON llm_cost_records(model_id);
CREATE INDEX IF NOT EXISTS idx_cost_records_recorded_at ON llm_cost_records(recorded_at DESC);

CREATE TABLE IF NOT EXISTS model_pricing (
    id                  TEXT PRIMARY KEY,
    provider            TEXT NOT NULL,
    model_id            TEXT NOT NULL,
    display_name        TEXT NOT NULL,
    input_per_million   REAL NOT NULL DEFAULT 0.0,
    output_per_million  REAL NOT NULL DEFAULT 0.0,
    effective_date      TEXT,
    verified_at         TEXT,
    source_url          TEXT,
    updated_at          TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(provider, model_id)
);

CREATE INDEX IF NOT EXISTS idx_model_pricing_provider ON model_pricing(provider);
"""

# Migration SQL for v13 — Budget limits
MIGRATION_V13_SQL = """
CREATE TABLE IF NOT EXISTS agent_budgets (
    agent_id        TEXT PRIMARY KEY,
    daily_budget_usd REAL NOT NULL,
    budget_action   TEXT NOT NULL DEFAULT 'warn' CHECK (budget_action IN ('warn', 'block')),
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"""

# Migration SQL for v14 — Cached token breakdown
MIGRATION_V14_SQL = """
ALTER TABLE llm_cost_records ADD COLUMN input_cached_tokens INTEGER NOT NULL DEFAULT 0;
"""

# Migration SQL for v2
MIGRATION_V2_SQL = """
-- Schema Version 2: Add community rules cache

CREATE TABLE IF NOT EXISTS community_rules (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    category TEXT NOT NULL,
    description TEXT NOT NULL,
    severity TEXT NOT NULL CHECK (severity IN ('low', 'medium', 'high', 'critical')),
    patterns TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    source_file TEXT,
    metadata TEXT,
    loaded_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_community_rules_category ON community_rules(category);
CREATE INDEX IF NOT EXISTS idx_community_rules_enabled ON community_rules(enabled);

-- Record migration
INSERT INTO schema_version (version, applied_at, description)
VALUES (2, CURRENT_TIMESTAMP, 'Add community rules cache table');
"""

# Migration SQL for v3
MIGRATION_V3_SQL = """
-- Schema Version 3: Add cloud mode fields to app_settings

ALTER TABLE app_settings ADD COLUMN cloud_mode_enabled INTEGER NOT NULL DEFAULT 0;
ALTER TABLE app_settings ADD COLUMN cloud_user_email TEXT DEFAULT NULL;
ALTER TABLE app_settings ADD COLUMN cloud_connected_at TIMESTAMP DEFAULT NULL;

-- Record migration
INSERT INTO schema_version (version, applied_at, description)
VALUES (3, CURRENT_TIMESTAMP, 'Add cloud mode fields to app_settings');
"""
