-- ============================================================================
-- SCHEMA: admin
-- TABLE: api_keys
-- DESCRIPTION: API Keys para acceso programatico
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 1 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para tipo de API key
DO $$ BEGIN
    CREATE TYPE admin.api_key_type AS ENUM (
        'public',           -- Solo lectura publica
        'private',          -- Acceso completo a recursos propios
        'admin',            -- Acceso administrativo
        'service',          -- Servicio a servicio
        'webhook',          -- Solo para recibir webhooks
        'readonly'          -- Solo lectura
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para estado de API key
DO $$ BEGIN
    CREATE TYPE admin.api_key_status AS ENUM (
        'active',           -- Activa y funcional
        'suspended',        -- Suspendida temporalmente
        'revoked',          -- Revocada permanentemente
        'expired'           -- Expirada
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de API Keys
CREATE TABLE IF NOT EXISTS admin.api_keys (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,

    -- Identificacion de la key
    name VARCHAR(100) NOT NULL,
    description TEXT,

    -- Key values (solo se almacena hash del secret)
    key_prefix VARCHAR(8) NOT NULL,             -- Primeros 8 caracteres para identificacion
    key_hash VARCHAR(255) NOT NULL,             -- Hash bcrypt del secret completo
    key_hint VARCHAR(4),                        -- Ultimos 4 caracteres para referencia

    -- Tipo y estado
    type admin.api_key_type NOT NULL DEFAULT 'private',
    status admin.api_key_status NOT NULL DEFAULT 'active',

    -- Permisos
    scopes JSONB NOT NULL DEFAULT '["read"]'::JSONB,  -- Array de scopes permitidos
    permissions JSONB DEFAULT '{}'::JSONB,            -- Permisos granulares

    -- Restricciones
    allowed_ips INET[],                         -- IPs permitidas (NULL = todas)
    allowed_origins TEXT[],                     -- Origenes CORS permitidos
    allowed_user_agents TEXT[],                 -- User agents permitidos

    -- Rate limiting
    rate_limit_per_minute INTEGER DEFAULT 60,
    rate_limit_per_hour INTEGER DEFAULT 1000,
    rate_limit_per_day INTEGER DEFAULT 10000,

    -- Uso
    last_used_at TIMESTAMPTZ,
    last_used_ip INET,
    last_used_user_agent TEXT,
    total_requests BIGINT NOT NULL DEFAULT 0,
    total_errors BIGINT NOT NULL DEFAULT 0,

    -- Tracking de uso diario
    requests_today INTEGER NOT NULL DEFAULT 0,
    requests_today_date DATE DEFAULT CURRENT_DATE,

    -- Expiracion
    expires_at TIMESTAMPTZ,

    -- Rotacion
    previous_key_hash VARCHAR(255),             -- Hash anterior durante rotacion
    previous_key_valid_until TIMESTAMPTZ,       -- Validez del key anterior
    rotated_at TIMESTAMPTZ,
    rotation_count INTEGER NOT NULL DEFAULT 0,

    -- Auditoria
    created_by UUID REFERENCES users.users(id),
    revoked_by UUID REFERENCES users.users(id),
    revoked_at TIMESTAMPTZ,
    revocation_reason TEXT,

    -- Metadata
    environment VARCHAR(20) DEFAULT 'production',  -- 'development', 'staging', 'production'
    metadata JSONB DEFAULT '{}'::JSONB,

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Constraints
    CONSTRAINT api_keys_unique_name_per_user UNIQUE (user_id, name),
    CONSTRAINT api_keys_unique_prefix UNIQUE (key_prefix)
);

COMMENT ON TABLE admin.api_keys IS
'API Keys para acceso programatico a la plataforma';

COMMENT ON COLUMN admin.api_keys.key_prefix IS
'Primeros 8 caracteres de la key para identificacion sin exponer el secret';

COMMENT ON COLUMN admin.api_keys.key_hash IS
'Hash bcrypt del secret completo (el secret solo se muestra una vez al crear)';

COMMENT ON COLUMN admin.api_keys.scopes IS
'Array de scopes: ["read", "write", "trade", "admin"]';

-- Indices
CREATE INDEX IF NOT EXISTS idx_api_keys_tenant
    ON admin.api_keys(tenant_id);

CREATE INDEX IF NOT EXISTS idx_api_keys_user
    ON admin.api_keys(user_id);

CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
    ON admin.api_keys(key_prefix);

CREATE INDEX IF NOT EXISTS idx_api_keys_status_active
    ON admin.api_keys(status)
    WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_api_keys_type
    ON admin.api_keys(type);

CREATE INDEX IF NOT EXISTS idx_api_keys_expires
    ON admin.api_keys(expires_at)
    WHERE expires_at IS NOT NULL AND status = 'active';

CREATE INDEX IF NOT EXISTS idx_api_keys_last_used
    ON admin.api_keys(last_used_at DESC)
    WHERE status = 'active';

-- Trigger para updated_at
DROP TRIGGER IF EXISTS api_key_updated_at ON admin.api_keys;
CREATE TRIGGER api_key_updated_at
    BEFORE UPDATE ON admin.api_keys
    FOR EACH ROW
    EXECUTE FUNCTION admin.update_admin_timestamp();

-- Trigger para resetear contador diario
CREATE OR REPLACE FUNCTION admin.reset_daily_requests()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.requests_today_date < CURRENT_DATE THEN
        NEW.requests_today := 0;
        NEW.requests_today_date := CURRENT_DATE;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS api_key_reset_daily ON admin.api_keys;
CREATE TRIGGER api_key_reset_daily
    BEFORE UPDATE ON admin.api_keys
    FOR EACH ROW
    EXECUTE FUNCTION admin.reset_daily_requests();

-- Funcion para validar API key
CREATE OR REPLACE FUNCTION admin.validate_api_key(
    p_key_prefix VARCHAR(8),
    p_client_ip INET DEFAULT NULL
)
RETURNS TABLE (
    is_valid BOOLEAN,
    key_id UUID,
    user_id UUID,
    tenant_id UUID,
    key_type admin.api_key_type,
    scopes JSONB,
    rate_limit_remaining INTEGER,
    error_message TEXT
) AS $$
DECLARE
    v_key RECORD;
    v_rate_remaining INTEGER;
BEGIN
    -- Buscar key por prefijo
    SELECT * INTO v_key
    FROM admin.api_keys ak
    WHERE ak.key_prefix = p_key_prefix
      AND ak.status = 'active'
    LIMIT 1;

    IF v_key IS NULL THEN
        RETURN QUERY SELECT
            FALSE, NULL::UUID, NULL::UUID, NULL::UUID,
            NULL::admin.api_key_type, NULL::JSONB, NULL::INTEGER,
            'API key not found or inactive'::TEXT;
        RETURN;
    END IF;

    -- Verificar expiracion
    IF v_key.expires_at IS NOT NULL AND v_key.expires_at < NOW() THEN
        -- Marcar como expirada
        UPDATE admin.api_keys SET status = 'expired' WHERE id = v_key.id;
        RETURN QUERY SELECT
            FALSE, NULL::UUID, NULL::UUID, NULL::UUID,
            NULL::admin.api_key_type, NULL::JSONB, NULL::INTEGER,
            'API key has expired'::TEXT;
        RETURN;
    END IF;

    -- Verificar IP si hay restricciones
    IF v_key.allowed_ips IS NOT NULL AND array_length(v_key.allowed_ips, 1) > 0 THEN
        IF p_client_ip IS NULL OR NOT (p_client_ip = ANY(v_key.allowed_ips)) THEN
            RETURN QUERY SELECT
                FALSE, NULL::UUID, NULL::UUID, NULL::UUID,
                NULL::admin.api_key_type, NULL::JSONB, NULL::INTEGER,
                'IP address not allowed'::TEXT;
            RETURN;
        END IF;
    END IF;

    -- Verificar rate limit diario
    v_rate_remaining := v_key.rate_limit_per_day - v_key.requests_today;
    IF v_rate_remaining <= 0 THEN
        RETURN QUERY SELECT
            FALSE, v_key.id, v_key.user_id, v_key.tenant_id,
            v_key.type, v_key.scopes, 0,
            'Daily rate limit exceeded'::TEXT;
        RETURN;
    END IF;

    -- Key valida - actualizar uso
    UPDATE admin.api_keys
    SET
        last_used_at = NOW(),
        last_used_ip = p_client_ip,
        total_requests = total_requests + 1,
        requests_today = requests_today + 1
    WHERE id = v_key.id;

    RETURN QUERY SELECT
        TRUE, v_key.id, v_key.user_id, v_key.tenant_id,
        v_key.type, v_key.scopes, v_rate_remaining - 1,
        NULL::TEXT;
END;
$$ LANGUAGE plpgsql;

-- Funcion para revocar API key
CREATE OR REPLACE FUNCTION admin.revoke_api_key(
    p_key_id UUID,
    p_revoked_by UUID,
    p_reason TEXT DEFAULT NULL
)
RETURNS BOOLEAN AS $$
BEGIN
    UPDATE admin.api_keys
    SET
        status = 'revoked',
        revoked_by = p_revoked_by,
        revoked_at = NOW(),
        revocation_reason = p_reason
    WHERE id = p_key_id
      AND status = 'active';

    RETURN FOUND;
END;
$$ LANGUAGE plpgsql;

-- Funcion para rotar API key (genera nuevo secret, mantiene el anterior temporalmente)
CREATE OR REPLACE FUNCTION admin.rotate_api_key(
    p_key_id UUID,
    p_new_key_hash VARCHAR(255),
    p_grace_period_hours INTEGER DEFAULT 24
)
RETURNS BOOLEAN AS $$
BEGIN
    UPDATE admin.api_keys
    SET
        previous_key_hash = key_hash,
        previous_key_valid_until = NOW() + (p_grace_period_hours || ' hours')::INTERVAL,
        key_hash = p_new_key_hash,
        rotated_at = NOW(),
        rotation_count = rotation_count + 1
    WHERE id = p_key_id
      AND status = 'active';

    RETURN FOUND;
END;
$$ LANGUAGE plpgsql;

-- Vista de API keys activas
CREATE OR REPLACE VIEW admin.v_active_api_keys AS
SELECT
    ak.id,
    ak.tenant_id,
    ak.user_id,
    u.email AS user_email,
    ak.name,
    ak.key_prefix,
    ak.key_hint,
    ak.type,
    ak.scopes,
    ak.rate_limit_per_day,
    ak.requests_today,
    ak.total_requests,
    ak.last_used_at,
    ak.expires_at,
    ak.created_at
FROM admin.api_keys ak
JOIN users.users u ON ak.user_id = u.id
WHERE ak.status = 'active'
ORDER BY ak.last_used_at DESC NULLS LAST;

-- Vista de uso de API keys por dia
CREATE OR REPLACE VIEW admin.v_api_key_usage AS
SELECT
    ak.id,
    ak.name,
    ak.key_prefix,
    ak.type,
    ak.total_requests,
    ak.total_errors,
    ak.requests_today,
    CASE WHEN ak.rate_limit_per_day > 0
         THEN (ak.requests_today::DECIMAL / ak.rate_limit_per_day * 100)::DECIMAL(5,2)
         ELSE 0
    END AS usage_percent,
    ak.last_used_at,
    ak.last_used_ip
FROM admin.api_keys ak
WHERE ak.status = 'active'
ORDER BY ak.total_requests DESC;

-- RLS Policy para multi-tenancy
ALTER TABLE admin.api_keys ENABLE ROW LEVEL SECURITY;

CREATE POLICY api_keys_tenant_isolation ON admin.api_keys
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Los usuarios solo pueden ver sus propias API keys
CREATE POLICY api_keys_user_isolation ON admin.api_keys
    FOR SELECT
    USING (user_id = current_setting('app.current_user_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON admin.api_keys TO trading_app;
GRANT SELECT ON admin.api_keys TO trading_readonly;
GRANT SELECT ON admin.v_active_api_keys TO trading_app;
GRANT SELECT ON admin.v_api_key_usage TO trading_app;
GRANT EXECUTE ON FUNCTION admin.validate_api_key TO trading_app;
GRANT EXECUTE ON FUNCTION admin.revoke_api_key TO trading_app;
GRANT EXECUTE ON FUNCTION admin.rotate_api_key TO trading_app;
