-- ============================================================================
-- DAARION Agent Memory System - PostgreSQL Schema
-- Version: 1.0.0
-- Date: 2026-01-10
-- 
-- Трирівнева пам'ять агентів:
--   1. Short-term: conversation_events (робочий буфер)
--   2. Mid-term: thread_summaries (сесійна/тематична)
--   3. Long-term: long_term_memory_items (персональна/проектна)
-- ============================================================================

-- Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ============================================================================
-- CORE ENTITIES
-- ============================================================================

-- Organizations (top-level tenant)
CREATE TABLE IF NOT EXISTS organizations (
    org_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(255) NOT NULL,
    settings JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Workspaces (projects within org)
CREATE TABLE IF NOT EXISTS workspaces (
    workspace_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    settings JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_workspaces_org ON workspaces(org_id);

-- Users
CREATE TABLE IF NOT EXISTS users (
    user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
    external_id VARCHAR(255), -- for SSO/OAuth mapping
    email VARCHAR(255),
    display_name VARCHAR(255),
    preferences JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(org_id, external_id)
);

CREATE INDEX idx_users_org ON users(org_id);
CREATE INDEX idx_users_email ON users(email);

-- Agents
CREATE TABLE IF NOT EXISTS agents (
    agent_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(50) NOT NULL, -- 'assistant', 'specialist', 'coordinator'
    model VARCHAR(100), -- 'claude-3-opus', 'gpt-4', etc.
    system_prompt TEXT,
    capabilities JSONB DEFAULT '[]', -- ['code', 'search', 'memory', 'tools']
    settings JSONB DEFAULT '{}',
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_agents_org ON agents(org_id);
CREATE INDEX idx_agents_type ON agents(type);

-- ============================================================================
-- CONVERSATION LAYER (Short-term Memory)
-- ============================================================================

-- Conversation threads
CREATE TABLE IF NOT EXISTS conversation_threads (
    thread_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    org_id UUID NOT NULL REFERENCES organizations(org_id) ON DELETE CASCADE,
    workspace_id UUID REFERENCES workspaces(workspace_id) ON DELETE SET NULL,
    user_id UUID NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
    agent_id UUID REFERENCES agents(agent_id) ON DELETE SET NULL,
    
    title VARCHAR(500),
    status VARCHAR(50) DEFAULT 'active', -- 'active', 'archived', 'completed'
    
    -- Metadata
    tags JSONB DEFAULT '[]',
    metadata JSONB DEFAULT '{}',
    
    -- Stats
    message_count INTEGER DEFAULT 0,
    total_tokens INTEGER DEFAULT 0,
    
    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    last_activity_at TIMESTAMPTZ DEFAULT NOW(),
    archived_at TIMESTAMPTZ
);

CREATE INDEX idx_threads_org ON conversation_threads(org_id);
CREATE INDEX idx_threads_workspace ON conversation_threads(workspace_id);
CREATE INDEX idx_threads_user ON conversation_threads(user_id);
CREATE INDEX idx_threads_agent ON conversation_threads(agent_id);
CREATE INDEX idx_threads_status ON conversation_threads(status);
CREATE INDEX idx_threads_last_activity ON conversation_threads(last_activity_at DESC);

-- Conversation events (Event Log - source of truth)
CREATE TABLE IF NOT EXISTS conversation_events (
    event_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    thread_id UUID NOT NULL REFERENCES conversation_threads(thread_id) ON DELETE CASCADE,
    
    -- Event type
    event_type VARCHAR(50) NOT NULL, -- 'message', 'tool_call', 'tool_result', 'decision', 'summary', 'memory_write', 'memory_retract', 'error'
    
    -- For messages
    role VARCHAR(20), -- 'user', 'assistant', 'system', 'tool'
    content TEXT,
    
    -- For tool calls
    tool_name VARCHAR(100),
    tool_input JSONB,
    tool_output JSONB,
    
    -- Structured payload (flexible)
    payload JSONB DEFAULT '{}',
    
    -- Token tracking
    token_count INTEGER,
    
    -- Metadata
    model_used VARCHAR(100),
    latency_ms INTEGER,
    metadata JSONB DEFAULT '{}',
    
    -- Ordering
    sequence_num SERIAL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_events_thread ON conversation_events(thread_id);
CREATE INDEX idx_events_type ON conversation_events(event_type);
CREATE INDEX idx_events_created ON conversation_events(created_at DESC);
CREATE INDEX idx_events_thread_seq ON conversation_events(thread_id, sequence_num);

-- ============================================================================
-- SUMMARY LAYER (Mid-term Memory)
-- ============================================================================

-- Thread summaries (rolling compression)
CREATE TABLE IF NOT EXISTS thread_summaries (
    summary_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    thread_id UUID NOT NULL REFERENCES conversation_threads(thread_id) ON DELETE CASCADE,
    
    -- Version tracking
    version INTEGER NOT NULL DEFAULT 1,
    
    -- Summary content
    summary_text TEXT NOT NULL,
    
    -- Structured state
    state JSONB DEFAULT '{}', -- {goals: [], decisions: [], open_questions: [], next_steps: [], key_facts: []}
    
    -- Coverage
    events_from_seq INTEGER, -- first event sequence included
    events_to_seq INTEGER,   -- last event sequence included
    events_count INTEGER,
    
    -- Token info
    original_tokens INTEGER, -- tokens before compression
    summary_tokens INTEGER,  -- tokens after compression
    compression_ratio FLOAT,
    
    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    
    UNIQUE(thread_id, version)
);

CREATE INDEX idx_summaries_thread ON thread_summaries(thread_id);
CREATE INDEX idx_summaries_thread_version ON thread_summaries(thread_id, version DESC);

-- ============================================================================
-- LONG-TERM MEMORY LAYER
-- ============================================================================

-- Memory categories enum
CREATE TYPE memory_category AS ENUM (
    'preference',      -- user likes/dislikes
    'identity',        -- who the user is
    'constraint',      -- limitations, rules
    'project_fact',    -- project-specific knowledge
    'relationship',    -- connections between entities
    'skill',           -- user capabilities
    'goal',            -- user objectives
    'context',         -- situational info
    'feedback'         -- user corrections/confirmations
);

-- Retention policy enum
CREATE TYPE retention_policy AS ENUM (
    'permanent',       -- keep until explicitly deleted
    'session',         -- delete after session
    'ttl_days',        -- delete after N days
    'until_revoked'    -- keep until user revokes
);

-- Long-term memory items
CREATE TABLE IF NOT EXISTS long_term_memory_items (
    memory_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    
    -- Scope (all nullable for flexibility)
    org_id UUID REFERENCES organizations(org_id) ON DELETE CASCADE,
    workspace_id UUID REFERENCES workspaces(workspace_id) ON DELETE SET NULL,
    user_id UUID REFERENCES users(user_id) ON DELETE CASCADE,
    agent_id UUID REFERENCES agents(agent_id) ON DELETE SET NULL, -- null = global for user
    
    -- Content
    category memory_category NOT NULL,
    fact_text TEXT NOT NULL, -- atomic statement
    fact_embedding_id VARCHAR(100), -- reference to Qdrant point ID
    
    -- Confidence & validation
    confidence FLOAT DEFAULT 0.8 CHECK (confidence >= 0 AND confidence <= 1),
    is_verified BOOLEAN DEFAULT false,
    verification_count INTEGER DEFAULT 0,
    
    -- Source tracking
    source_event_id UUID REFERENCES conversation_events(event_id) ON DELETE SET NULL,
    source_thread_id UUID REFERENCES conversation_threads(thread_id) ON DELETE SET NULL,
    extraction_method VARCHAR(50), -- 'explicit', 'inferred', 'confirmed', 'imported'
    
    -- Lifecycle
    valid_from TIMESTAMPTZ DEFAULT NOW(),
    valid_to TIMESTAMPTZ, -- null = currently valid
    last_confirmed_at TIMESTAMPTZ,
    last_used_at TIMESTAMPTZ,
    use_count INTEGER DEFAULT 0,
    
    -- Privacy & retention
    is_sensitive BOOLEAN DEFAULT false,
    retention retention_policy DEFAULT 'until_revoked',
    ttl_days INTEGER, -- if retention = 'ttl_days'
    
    -- Metadata
    tags JSONB DEFAULT '[]',
    metadata JSONB DEFAULT '{}',
    
    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Indexes for memory retrieval
CREATE INDEX idx_memory_org ON long_term_memory_items(org_id);
CREATE INDEX idx_memory_workspace ON long_term_memory_items(workspace_id);
CREATE INDEX idx_memory_user ON long_term_memory_items(user_id);
CREATE INDEX idx_memory_agent ON long_term_memory_items(agent_id);
CREATE INDEX idx_memory_category ON long_term_memory_items(category);
CREATE INDEX idx_memory_user_agent ON long_term_memory_items(user_id, agent_id);
CREATE INDEX idx_memory_valid ON long_term_memory_items(valid_from, valid_to);
CREATE INDEX idx_memory_confidence ON long_term_memory_items(confidence DESC);
CREATE INDEX idx_memory_created ON long_term_memory_items(created_at DESC);

-- GIN index for tags search
CREATE INDEX idx_memory_tags ON long_term_memory_items USING GIN (tags);

-- Memory feedback (user corrections)
CREATE TABLE IF NOT EXISTS memory_feedback (
    feedback_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    memory_id UUID NOT NULL REFERENCES long_term_memory_items(memory_id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
    
    action VARCHAR(20) NOT NULL, -- 'confirm', 'reject', 'edit', 'delete'
    old_value TEXT,
    new_value TEXT,
    reason TEXT,
    
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_feedback_memory ON memory_feedback(memory_id);
CREATE INDEX idx_feedback_user ON memory_feedback(user_id);

-- ============================================================================
-- HELPER VIEWS
-- ============================================================================

-- Active memories for a user (across all agents)
CREATE OR REPLACE VIEW v_active_user_memories AS
SELECT 
    m.*,
    a.name as agent_name
FROM long_term_memory_items m
LEFT JOIN agents a ON m.agent_id = a.agent_id
WHERE m.valid_to IS NULL 
  AND m.confidence >= 0.5
ORDER BY m.confidence DESC, m.last_used_at DESC NULLS LAST;

-- Recent conversations with summaries
CREATE OR REPLACE VIEW v_recent_conversations AS
SELECT 
    t.thread_id,
    t.title,
    t.user_id,
    t.agent_id,
    t.message_count,
    t.last_activity_at,
    s.summary_text,
    s.state
FROM conversation_threads t
LEFT JOIN LATERAL (
    SELECT summary_text, state 
    FROM thread_summaries 
    WHERE thread_id = t.thread_id 
    ORDER BY version DESC 
    LIMIT 1
) s ON true
WHERE t.status = 'active'
ORDER BY t.last_activity_at DESC;

-- ============================================================================
-- FUNCTIONS
-- ============================================================================

-- Update thread stats after new event
CREATE OR REPLACE FUNCTION update_thread_stats()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE conversation_threads
    SET 
        message_count = message_count + CASE WHEN NEW.event_type = 'message' THEN 1 ELSE 0 END,
        total_tokens = total_tokens + COALESCE(NEW.token_count, 0),
        last_activity_at = NOW()
    WHERE thread_id = NEW.thread_id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_thread_stats
AFTER INSERT ON conversation_events
FOR EACH ROW EXECUTE FUNCTION update_thread_stats();

-- Update memory usage stats
CREATE OR REPLACE FUNCTION update_memory_usage()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE long_term_memory_items
    SET 
        use_count = use_count + 1,
        last_used_at = NOW()
    WHERE memory_id = NEW.memory_id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Auto-update updated_at
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_organizations_updated
BEFORE UPDATE ON organizations
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_workspaces_updated
BEFORE UPDATE ON workspaces
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_users_updated
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_agents_updated
BEFORE UPDATE ON agents
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER trg_memory_updated
BEFORE UPDATE ON long_term_memory_items
FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ============================================================================
-- INITIAL DATA
-- ============================================================================

-- Default organization
INSERT INTO organizations (org_id, name, settings) 
VALUES (
    'a0000000-0000-0000-0000-000000000001',
    'DAARION',
    '{"tier": "enterprise", "features": ["memory", "multi-agent", "knowledge-base"]}'
) ON CONFLICT DO NOTHING;

-- Default workspace
INSERT INTO workspaces (workspace_id, org_id, name, description)
VALUES (
    'b0000000-0000-0000-0000-000000000001',
    'a0000000-0000-0000-0000-000000000001',
    'MicroDAO',
    'Main development workspace for DAARION project'
) ON CONFLICT DO NOTHING;

-- Default user (Ivan)
INSERT INTO users (user_id, org_id, external_id, display_name, preferences)
VALUES (
    'c0000000-0000-0000-0000-000000000001',
    'a0000000-0000-0000-0000-000000000001',
    'ivan',
    'Ivan Tytar',
    '{"language": "uk", "timezone": "Europe/Kyiv"}'
) ON CONFLICT DO NOTHING;

-- Default agents
INSERT INTO agents (agent_id, org_id, name, type, model, capabilities) VALUES
(
    'd0000000-0000-0000-0000-000000000001',
    'a0000000-0000-0000-0000-000000000001',
    'Claude Assistant',
    'assistant',
    'claude-3-opus',
    '["code", "memory", "tools", "search"]'
),
(
    'd0000000-0000-0000-0000-000000000002',
    'a0000000-0000-0000-0000-000000000001',
    'Code Specialist',
    'specialist',
    'claude-3-opus',
    '["code", "review", "refactor"]'
),
(
    'd0000000-0000-0000-0000-000000000003',
    'a0000000-0000-0000-0000-000000000001',
    'DevOps Agent',
    'specialist',
    'claude-3-opus',
    '["infrastructure", "deployment", "monitoring"]'
)
ON CONFLICT DO NOTHING;

-- ============================================================================
-- COMMENTS
-- ============================================================================

COMMENT ON TABLE conversation_events IS 'Event log for all conversation activities - source of truth';
COMMENT ON TABLE thread_summaries IS 'Rolling summaries for context compression (mid-term memory)';
COMMENT ON TABLE long_term_memory_items IS 'Persistent facts about users/projects (long-term memory)';
COMMENT ON COLUMN long_term_memory_items.fact_text IS 'Atomic statement - one fact per row';
COMMENT ON COLUMN long_term_memory_items.confidence IS 'Confidence score 0-1, increases with confirmations';
COMMENT ON COLUMN long_term_memory_items.fact_embedding_id IS 'Reference to Qdrant vector point ID';
