-- =============================================================================
-- OMNIOIL: FULL DATABASE SCHEMA (Consolidated)
-- FECHA: 2026-04-10
-- DESCRIPCIÓN: Esquema maestro que integra Mantenimiento de Roles, Usuarios, 
--              Gestion de Agentes, Activos, Telemetría (TimescaleDB) y Seguridad MQTT.
-- =============================================================================

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

-- =============================================================================
-- MÓDULO 1: SEGURIDAD Y ACCESO (DASHBOARD)
-- =============================================================================

CREATE TABLE IF NOT EXISTS roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    permissions JSONB NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Semilla de roles
INSERT INTO roles (name, permissions) VALUES
('admin',      '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": true, "USERS_ADMIN": true}'::jsonb),
('supervisor', '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": true, "USERS_ADMIN": false}'::jsonb),
('operador',   '{"SYNC_WRITE": true, "DATA_READ": true, "CAMPO_READ": false, "USERS_ADMIN": false}'::jsonb),
('anh_reader', '{"DATA_READ": true, "CAMPO_READ": true}'::jsonb)
ON CONFLICT (name) DO NOTHING;

CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    email VARCHAR(100) UNIQUE NOT NULL,
    full_name VARCHAR(200),
    password_hash VARCHAR(255) NOT NULL,
    role_id INT NOT NULL REFERENCES roles(id),
    is_active BOOLEAN DEFAULT true,
    last_login TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Soporte para sesiones seguras (JWT Refresh)
CREATE TABLE IF NOT EXISTS refresh_tokens (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash TEXT NOT NULL UNIQUE,
    expires_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    revoked_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id);

-- =============================================================================
-- MÓDULO 2: JERARQUÍA OPERATIVA (EDGE)
-- =============================================================================

CREATE TABLE IF NOT EXISTS edge_projects (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(100) NOT NULL UNIQUE,
    client VARCHAR(150) NOT NULL,
    description TEXT,
    metadata JSONB DEFAULT '{}'::jsonb,
    is_active BOOLEAN DEFAULT true,
    installer_status VARCHAR(50) DEFAULT 'NOT_STARTED',
    installer_url TEXT,
    last_health_report JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT check_installer_status CHECK (installer_status IN ('NOT_STARTED', 'QUEUED', 'BUILDING', 'READY', 'COMPLETED', 'FAILED'))
);

CREATE TABLE IF NOT EXISTS edge_assets (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE,
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    asset_type VARCHAR(20) NOT NULL
        CHECK (asset_type IN ('POZO', 'TANQUE', 'MEDIDOR_GAS', 'AGUA', 'OTRO')),
    is_active BOOLEAN DEFAULT true,
    metadata JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS edge_devices (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    asset_id UUID NOT NULL REFERENCES edge_assets(id) ON DELETE CASCADE,
    name VARCHAR(100) NOT NULL,
    protocol VARCHAR(30) NOT NULL
        CHECK (protocol IN ('MODBUS_TCP', 'MODBUS_RTU', 'OPC_UA', 'MQTT', 'ETHERNET_IP', 'PROFINET')),
    host VARCHAR(255) NOT NULL,
    port INTEGER NOT NULL,
    protocol_config JSONB DEFAULT '{}'::jsonb,
    is_enabled BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS edge_variables (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    device_id UUID NOT NULL REFERENCES edge_devices(id) ON DELETE CASCADE,
    address VARCHAR(255) NOT NULL,
    data_type VARCHAR(30) NOT NULL
        CHECK (data_type IN ('bool', 'int16', 'uint16', 'int32', 'uint32', 'float32', 'float64', 'string')),
    alias VARCHAR(100) NOT NULL,
    unit VARCHAR(30),
    polling_interval_ms INTEGER DEFAULT 5000,
    byte_order VARCHAR(10) DEFAULT 'BE'
        CHECK (byte_order IN ('BE', 'LE', 'BE_SWAP', 'LE_SWAP')),
    is_enabled BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- =============================================================================
-- MÓDULO 3: TELEMETRÍA (TimescaleDB)
-- =============================================================================

CREATE TABLE IF NOT EXISTS telemetry_raw (
    time TIMESTAMPTZ NOT NULL,
    project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE,
    asset_id UUID REFERENCES edge_assets(id) ON DELETE CASCADE,
    data JSONB NOT NULL,
    PRIMARY KEY (time, project_id, asset_id)
);

-- Convertir a Hipertabla (TimescaleDB Optimize)
SELECT create_hypertable('telemetry_raw', 'time', if_not_exists => TRUE);

CREATE TABLE IF NOT EXISTS agent_logs (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE,
    agent_hostname VARCHAR(255),
    level VARCHAR(20),
    category VARCHAR(50),
    message TEXT,
    context JSONB,
    timestamp TIMESTAMPTZ DEFAULT NOW()
);

-- =============================================================================
-- MÓDULO 4: MQTT AUTH & ACL (Mosquitto Integration)
-- =============================================================================

CREATE TABLE IF NOT EXISTS mqtt_users (
    username VARCHAR(100) PRIMARY KEY,
    password_hash VARCHAR(255) NOT NULL,
    is_admin BOOLEAN DEFAULT false,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS mqtt_acls (
    id SERIAL PRIMARY KEY,
    username VARCHAR(100) NOT NULL REFERENCES mqtt_users(username) ON DELETE CASCADE,
    topic VARCHAR(500) NOT NULL,
    rw INT DEFAULT 1, -- 1 = read, 2 = write, 3 = readwrite
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(username, topic)
);

-- =============================================================================
-- MÓDULO 5: INFRAESTRUCTURA DE DESPLIEGUE Y EVENTOS
-- =============================================================================

CREATE TABLE IF NOT EXISTS edge_deployments (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    project_id UUID NOT NULL REFERENCES edge_projects(id) ON DELETE CASCADE,
    image_tag VARCHAR(500) NOT NULL,
    image_hash VARCHAR(100),
    status VARCHAR(30) NOT NULL DEFAULT 'BUILDING',
    previous_image_tag VARCHAR(500),
    previous_image_hash VARCHAR(100),
    config_snapshot JSONB,
    build_metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
    error_message TEXT,
    deployed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS domain_outbox (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    event_type VARCHAR(100) NOT NULL,
    payload JSONB NOT NULL,
    status VARCHAR(20) DEFAULT 'PENDING',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- =============================================================================
-- MÓDULO 6: MOVIMIENTOS OPERATIVOS (ANH 0651)
-- =============================================================================

CREATE TABLE IF NOT EXISTS operation_movements (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    asset_id UUID NOT NULL REFERENCES edge_assets(id) ON DELETE CASCADE,
    movement_type VARCHAR(30) NOT NULL
        CHECK (movement_type IN ('VENTA', 'RECIBO', 'TRANSFERENCIA', 'QUEMA', 'CONSUMO')),
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ NOT NULL,
    volumen_neto DECIMAL(18, 4),
    details JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    CONSTRAINT check_movement_times CHECK (end_time >= start_time)
);
