-- Migration 039: Node Registry for Self-Healing
-- Створення node_registry як єдиного джерела істини для нод
-- Частина TASK_PHASE_NODE_SELF_HEALING_v1

-- ============================================================================
-- 1. Створити таблицю node_registry
-- ============================================================================

CREATE TABLE IF NOT EXISTS node_registry (
    id text PRIMARY KEY,                    -- node_id (напр. node-2-macbook-m4max)
    name text NOT NULL,                     -- Людська назва ноди
    hostname text,                          -- Hostname ноди
    environment text NOT NULL CHECK (environment IN ('production', 'development', 'staging')),
    roles text[] NOT NULL DEFAULT '{}',     -- ['gpu', 'ai_runtime', 'storage', ...]
    description text,                       -- Опис ноди
    owner_id text,                          -- ID власника (user/microdao)
    config jsonb DEFAULT '{}',              -- Додаткова конфігурація
    is_active boolean NOT NULL DEFAULT true,
    registered_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    last_self_registration timestamptz,     -- Остання самореєстрація
    self_registration_count integer DEFAULT 0
);

-- Індекси
CREATE INDEX IF NOT EXISTS idx_node_registry_active ON node_registry(is_active) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_node_registry_environment ON node_registry(environment);
CREATE INDEX IF NOT EXISTS idx_node_registry_updated ON node_registry(updated_at DESC);

-- ============================================================================
-- 2. Оновити node_cache - додати зв'язок з registry
-- ============================================================================

-- Перевірити що node_cache.node_id є foreign key до node_registry
-- (опційно, можна не додавати FK для гнучкості)

-- Додати поле для статусу self-healing
ALTER TABLE node_cache ADD COLUMN IF NOT EXISTS self_healing_status text DEFAULT 'healthy';
ALTER TABLE node_cache ADD COLUMN IF NOT EXISTS self_healing_last_check timestamptz;
ALTER TABLE node_cache ADD COLUMN IF NOT EXISTS self_healing_errors jsonb DEFAULT '[]';

-- ============================================================================
-- 3. Зареєструвати існуючі ноди
-- ============================================================================

-- NODE1: Hetzner GEX44 Production
INSERT INTO node_registry (
    id,
    name,
    hostname,
    environment,
    roles,
    description,
    is_active,
    registered_at,
    updated_at
) VALUES (
    'node-1-hetzner-gex44',
    'NODE1 — Hetzner GEX44',
    'node1.daarion.space',
    'production',
    ARRAY['production', 'gpu', 'ai_runtime', 'storage', 'matrix'],
    'Production server with RTX 4090, hosts Matrix Synapse, DAGI Router, main services',
    true,
    NOW(),
    NOW()
)
ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name,
    hostname = EXCLUDED.hostname,
    environment = EXCLUDED.environment,
    roles = EXCLUDED.roles,
    description = EXCLUDED.description,
    is_active = true,
    updated_at = NOW();

-- NODE2: MacBook Pro M4 Max Development
INSERT INTO node_registry (
    id,
    name,
    hostname,
    environment,
    roles,
    description,
    is_active,
    registered_at,
    updated_at
) VALUES (
    'node-2-macbook-m4max',
    'NODE2 — MacBook Pro M4 Max',
    'node2.local',
    'development',
    ARRAY['development', 'gpu', 'ai_runtime', 'testing'],
    'Development node with M4 Max GPU (40GB unified memory), local AI models',
    true,
    NOW(),
    NOW()
)
ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name,
    hostname = EXCLUDED.hostname,
    environment = EXCLUDED.environment,
    roles = EXCLUDED.roles,
    description = EXCLUDED.description,
    is_active = true,
    updated_at = NOW();

-- ============================================================================
-- 4. Переконатися що node_cache має записи для обох нод
-- ============================================================================

-- NODE1
INSERT INTO node_cache (node_id, last_heartbeat, self_healing_status)
VALUES ('node-1-hetzner-gex44', NOW(), 'healthy')
ON CONFLICT (node_id) DO UPDATE SET
    self_healing_status = 'healthy',
    self_healing_last_check = NOW();

-- NODE2  
INSERT INTO node_cache (node_id, last_heartbeat, self_healing_status)
VALUES ('node-2-macbook-m4max', NOW(), 'healthy')
ON CONFLICT (node_id) DO UPDATE SET
    self_healing_status = 'healthy',
    self_healing_last_check = NOW();

-- ============================================================================
-- 5. View для Node Directory (з'єднання registry + cache)
-- ============================================================================

CREATE OR REPLACE VIEW v_nodes_directory AS
SELECT 
    r.id,
    r.name,
    r.hostname,
    r.environment,
    r.roles,
    r.description,
    r.is_active,
    r.registered_at,
    r.updated_at,
    r.last_self_registration,
    -- Cache data (metrics)
    c.cpu_model,
    c.cpu_cores,
    c.cpu_usage,
    c.gpu_model,
    c.gpu_vram_total,
    c.gpu_vram_used,
    c.ram_total,
    c.ram_used,
    c.disk_total,
    c.disk_used,
    c.agent_count_router,
    c.agent_count_system,
    c.last_heartbeat,
    c.dagi_router_url,
    c.guardian_agent_id,
    c.steward_agent_id,
    c.self_healing_status,
    c.self_healing_last_check,
    -- Derived fields
    CASE 
        WHEN c.last_heartbeat IS NULL THEN 'offline'
        WHEN c.last_heartbeat < NOW() - INTERVAL '10 minutes' THEN 'stale'
        ELSE 'online'
    END AS connection_status,
    EXTRACT(EPOCH FROM (NOW() - c.last_heartbeat)) / 60 AS heartbeat_age_minutes
FROM node_registry r
LEFT JOIN node_cache c ON c.node_id = r.id
WHERE r.is_active = true;

-- ============================================================================
-- 6. Функція для self-registration
-- ============================================================================

CREATE OR REPLACE FUNCTION fn_node_self_register(
    p_node_id text,
    p_name text,
    p_hostname text DEFAULT NULL,
    p_environment text DEFAULT 'development',
    p_roles text[] DEFAULT '{}'
) RETURNS jsonb AS $$
DECLARE
    v_result jsonb;
    v_is_new boolean := false;
BEGIN
    -- Перевірити чи нода вже існує
    IF NOT EXISTS (SELECT 1 FROM node_registry WHERE id = p_node_id) THEN
        v_is_new := true;
    END IF;
    
    -- Insert or update node_registry
    INSERT INTO node_registry (
        id, name, hostname, environment, roles,
        is_active, registered_at, updated_at,
        last_self_registration, self_registration_count
    ) VALUES (
        p_node_id, p_name, p_hostname, p_environment, p_roles,
        true, NOW(), NOW(), NOW(), 1
    )
    ON CONFLICT (id) DO UPDATE SET
        name = COALESCE(NULLIF(p_name, ''), node_registry.name),
        hostname = COALESCE(p_hostname, node_registry.hostname),
        environment = COALESCE(NULLIF(p_environment, ''), node_registry.environment),
        roles = CASE 
            WHEN array_length(p_roles, 1) > 0 THEN p_roles 
            ELSE node_registry.roles 
        END,
        is_active = true,
        updated_at = NOW(),
        last_self_registration = NOW(),
        self_registration_count = COALESCE(node_registry.self_registration_count, 0) + 1;
    
    -- Ensure node_cache entry exists
    INSERT INTO node_cache (node_id, last_heartbeat, self_healing_status)
    VALUES (p_node_id, NOW(), 'healthy')
    ON CONFLICT (node_id) DO UPDATE SET
        last_heartbeat = NOW(),
        self_healing_status = 'healthy',
        self_healing_last_check = NOW();
    
    -- Return result
    v_result := jsonb_build_object(
        'success', true,
        'node_id', p_node_id,
        'is_new', v_is_new,
        'message', CASE WHEN v_is_new THEN 'Node registered' ELSE 'Node updated' END
    );
    
    RETURN v_result;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- 7. Функція для оновлення heartbeat
-- ============================================================================

CREATE OR REPLACE FUNCTION fn_node_heartbeat(
    p_node_id text,
    p_metrics jsonb DEFAULT NULL
) RETURNS jsonb AS $$
DECLARE
    v_node_exists boolean;
BEGIN
    -- Перевірити чи нода зареєстрована
    SELECT EXISTS(SELECT 1 FROM node_registry WHERE id = p_node_id AND is_active = true)
    INTO v_node_exists;
    
    IF NOT v_node_exists THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Node not registered',
            'should_self_register', true
        );
    END IF;
    
    -- Оновити node_cache
    UPDATE node_cache SET
        last_heartbeat = NOW(),
        self_healing_status = 'healthy',
        cpu_usage = COALESCE((p_metrics->>'cpu_usage')::numeric, cpu_usage),
        gpu_vram_used = COALESCE((p_metrics->>'gpu_vram_used')::integer, gpu_vram_used),
        ram_used = COALESCE((p_metrics->>'ram_used')::integer, ram_used),
        disk_used = COALESCE((p_metrics->>'disk_used')::integer, disk_used),
        agent_count_router = COALESCE((p_metrics->>'agent_count_router')::integer, agent_count_router),
        agent_count_system = COALESCE((p_metrics->>'agent_count_system')::integer, agent_count_system)
    WHERE node_id = p_node_id;
    
    -- Також оновити updated_at в registry
    UPDATE node_registry SET updated_at = NOW()
    WHERE id = p_node_id;
    
    RETURN jsonb_build_object(
        'success', true,
        'node_id', p_node_id,
        'heartbeat_at', NOW()
    );
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- 8. Коментарі
-- ============================================================================

COMMENT ON TABLE node_registry IS 'Реєстр нод DAARION — єдине джерело істини для Node Directory';
COMMENT ON COLUMN node_registry.id IS 'Унікальний ідентифікатор ноди';
COMMENT ON COLUMN node_registry.roles IS 'Ролі ноди: gpu, ai_runtime, storage, matrix, development, production';
COMMENT ON COLUMN node_registry.last_self_registration IS 'Остання успішна самореєстрація ноди';
COMMENT ON COLUMN node_registry.self_registration_count IS 'Кількість разів, коли нода реєструвала себе';

COMMENT ON FUNCTION fn_node_self_register IS 'Самореєстрація ноди — викликається Node Bootstrap або Guardian';
COMMENT ON FUNCTION fn_node_heartbeat IS 'Heartbeat ноди з оновленням метрик';

COMMENT ON VIEW v_nodes_directory IS 'View для Node Directory — з''єднує registry + cache + derived статуси';

-- ============================================================================
-- 9. Результат
-- ============================================================================

SELECT 'Migration 039 completed: Node Registry for Self-Healing' AS result;

-- Показати зареєстровані ноди
SELECT 
    id,
    name,
    environment,
    roles,
    is_active
FROM node_registry
ORDER BY registered_at;

